@praxisui/page-builder 9.0.68-rc.0 → 9.0.68-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Inject, ChangeDetectionStrategy, Component, EventEmitter, Output, Input, inject, ElementRef, signal, computed, effect, Optional, input, output, Injector, viewChild, afterNextRender, afterEveryRender, InjectionToken, ENVIRONMENT_INITIALIZER, Injectable, ViewChild } from '@angular/core';
2
+ import { Inject, ChangeDetectionStrategy, Component, EventEmitter, Output, Input, inject, ElementRef, signal, computed, effect, Optional, input, output, Injector, viewChild, afterNextRender, afterEveryRender, ViewChild, InjectionToken, ENVIRONMENT_INITIALIZER, Injectable } from '@angular/core';
3
3
  import * as i2 from '@angular/material/button';
4
4
  import { MatButtonModule } from '@angular/material/button';
5
5
  import * as i1 from '@angular/material/dialog';
@@ -7,7 +7,7 @@ import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
7
7
  import * as i2$1 from '@angular/material/icon';
8
8
  import { MatIconModule } from '@angular/material/icon';
9
9
  import * as i2$2 from '@praxisui/core';
10
- import { PraxisIconDirective, providePraxisI18n, BUILTIN_WIDGET_SHELL_PRESETS, SETTINGS_EDITOR_SECTIONS_HOSTED, PraxisI18nService, getWidgetShellActionIdError, WidgetShellComponent, SettingsSectionTabsComponent, PraxisIconButtonComponent, GLOBAL_ACTION_CATALOG, ComponentMetadataRegistry, CompositionValidatorService, getGlobalActionCatalog, PRAXIS_GLOBAL_ACTION_CATALOG, SurfaceOpenActionEditorComponent, BUILTIN_PAGE_LAYOUT_PRESETS, BUILTIN_PAGE_THEME_PRESETS, SETTINGS_PANEL_DATA as SETTINGS_PANEL_DATA$1, PraxisJsonLogicService, NestedWidgetConfigAccessor, canonicalJsonSha256, ResourceDiscoveryService, ComponentRuntimeProfileService, buildApiUrl, deepMerge, generateId, ObservabilityDashboardService, PraxisRuntimeComponentObservationRegistryService, domainKnowledgeTimelineToRichContentDocument, DomainRuleService, DomainKnowledgeService, SETTINGS_PANEL_BRIDGE, DynamicWidgetPageComponent, DYNAMIC_PAGE_SHELL_EDITOR } from '@praxisui/core';
10
+ import { PraxisIconDirective, providePraxisI18n, BUILTIN_WIDGET_SHELL_PRESETS, SETTINGS_EDITOR_SECTIONS_HOSTED, PraxisI18nService, getWidgetShellActionIdError, WidgetShellComponent, SettingsSectionTabsComponent, PraxisIconButtonComponent, GLOBAL_ACTION_CATALOG, ComponentMetadataRegistry, CompositionValidatorService, getGlobalActionCatalog, PRAXIS_GLOBAL_ACTION_CATALOG, SurfaceOpenActionEditorComponent, BUILTIN_PAGE_LAYOUT_PRESETS, resolveWidgetPageLayoutPreset, BUILTIN_PAGE_THEME_PRESETS, prepareCanvasArrangement, SETTINGS_PANEL_DATA as SETTINGS_PANEL_DATA$1, validateCanvasArrangement, PraxisJsonLogicService, NestedWidgetConfigAccessor, canonicalJsonSha256, ResourceDiscoveryService, ComponentRuntimeProfileService, buildApiUrl, deepMerge, generateId, ObservabilityDashboardService, PraxisRuntimeComponentObservationRegistryService, domainKnowledgeTimelineToRichContentDocument, DomainRuleService, DomainKnowledgeService, SETTINGS_PANEL_BRIDGE, DynamicWidgetPageComponent, DYNAMIC_PAGE_SHELL_EDITOR } from '@praxisui/core';
11
11
  import * as i3 from '@angular/material/tooltip';
12
12
  import { MatTooltipModule } from '@angular/material/tooltip';
13
13
  import * as i4 from '@angular/material/form-field';
@@ -17,10 +17,10 @@ import { MatInputModule } from '@angular/material/input';
17
17
  import * as i1$1 from '@angular/forms';
18
18
  import { FormsModule, FormGroup, FormArray, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
19
19
  import * as i1$2 from '@angular/common';
20
- import { CommonModule } from '@angular/common';
20
+ import { CommonModule, NgTemplateOutlet } from '@angular/common';
21
21
  import * as i4$1 from '@angular/material/checkbox';
22
22
  import { MatCheckboxModule } from '@angular/material/checkbox';
23
- import * as i8 from '@angular/material/select';
23
+ import * as i9 from '@angular/material/select';
24
24
  import { MatSelectModule } from '@angular/material/select';
25
25
  import { BehaviorSubject, merge, timeout, map, catchError, throwError, firstValueFrom, from, share, concatMap, of, timer, takeUntil, Subject, tap, filter, take } from 'rxjs';
26
26
  import { SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
@@ -28,7 +28,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
28
28
  import { CdkTrapFocus } from '@angular/cdk/a11y';
29
29
  import * as i3$1 from '@angular/material/button-toggle';
30
30
  import { MatButtonToggleModule } from '@angular/material/button-toggle';
31
- import * as i8$1 from '@angular/material/tabs';
31
+ import * as i8 from '@angular/material/tabs';
32
32
  import { MatTabsModule } from '@angular/material/tabs';
33
33
  import { HttpClient, HttpHeaders } from '@angular/common/http';
34
34
  import { AgenticAuthoringTurnClientService, withAuthoringScopePolicyContextHints, toPraxisAssistantConversationMessages, PraxisAssistantTurnOrchestratorService, PraxisAssistantSessionRegistryService, createPraxisAssistantViewportLayout, PraxisAiAssistantShellComponent } from '@praxisui/ai';
@@ -361,6 +361,97 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
361
361
  }] } });
362
362
 
363
363
  const PRAXIS_PAGE_BUILDER_EN_US = {
364
+ "praxis.pageBuilder.studio.gridSize": "Size in columns × rows",
365
+ "praxis.pageBuilder.studio.visualTab": "Visual templates",
366
+ "praxis.pageBuilder.studio.layoutTab": "Layout templates",
367
+ "praxis.pageBuilder.studio.arrangeTab": "Arrange",
368
+ "praxis.pageBuilder.studio.settingsTab": "Adjustments",
369
+ "praxis.pageBuilder.studio.visualEyebrow": "Appearance",
370
+ "praxis.pageBuilder.studio.visualTitle": "Give the page a visual language",
371
+ "praxis.pageBuilder.studio.visualHelp": "Choose surfaces, density and chart styling together. Individual widget overrides still take precedence.",
372
+ "praxis.pageBuilder.studio.layoutEyebrow": "Composition model",
373
+ "praxis.pageBuilder.studio.layoutTitle": "Choose the structure for your use case",
374
+ "praxis.pageBuilder.studio.layoutHelp": "Compare the roles and default theme of each model. To redistribute the widgets already on this canvas, open Arrange.",
375
+ "praxis.pageBuilder.studio.inheritLayout": "Use inherited model",
376
+ "praxis.pageBuilder.studio.goArrange": "Arrange existing widgets",
377
+ "praxis.pageBuilder.studio.arrangeEyebrow": "Existing widgets",
378
+ "praxis.pageBuilder.studio.arrangeTitle": "Find the right balance of space",
379
+ "praxis.pageBuilder.studio.arrangeHelp": "Choose a distribution, compare the real positions, then use it in the draft. Widgets keep their reading order and content.",
380
+ "praxis.pageBuilder.studio.samplesHelp": "Illustrative samples using the preset tokens. Apply to see the result with your content.",
381
+ "praxis.pageBuilder.studio.shellTitle": "Customize widget surfaces",
382
+ "praxis.pageBuilder.studio.shellHelp": "Override the page theme’s default widget surface. Per-widget appearance settings keep their priority.",
383
+ "praxis.pageBuilder.studio.density.compact": "Compact",
384
+ "praxis.pageBuilder.studio.density.comfortable": "Comfortable",
385
+ "praxis.pageBuilder.studio.moreColumns": "Needs more grid columns",
386
+ "praxis.pageBuilder.studio.reviewTitle": "Compare before using",
387
+ "praxis.pageBuilder.studio.changedWidgets": "widgets change",
388
+ "praxis.pageBuilder.studio.before": "Before",
389
+ "praxis.pageBuilder.studio.after": "After",
390
+ "praxis.pageBuilder.studio.widgetDetails": "Widget positions and sizes",
391
+ "praxis.pageBuilder.studio.mapHelp": "Numbered map of your widgets. Positions and spans are to scale; content height is evaluated on the page.",
392
+ "praxis.pageBuilder.studio.rules": "Scope, devices and locked widgets",
393
+ "praxis.pageBuilder.studio.goLimits": "Review widget limits",
394
+ "praxis.pageBuilder.studio.theme.analytics-calm": "Analytical reading with quiet surfaces and comfortable spacing.",
395
+ "praxis.pageBuilder.studio.theme.workspace-balanced": "Balance for everyday work and data exploration.",
396
+ "praxis.pageBuilder.studio.theme.ops-monitoring": "Higher density and emphasis on status, queues and alerts.",
397
+ "praxis.pageBuilder.studio.theme.executive-command-center": "Compact hierarchy for executive overviews and decisions.",
398
+ "praxis.pageBuilder.studio.arrange.stack": "One lane per widget for sequential reading.",
399
+ "praxis.pageBuilder.studio.arrange.compare": "Two equal lanes for comparing information.",
400
+ "praxis.pageBuilder.studio.arrange.primary": "More space on the left, with support on the right.",
401
+ "praxis.pageBuilder.studio.arrange.secondary": "Support on the left and more space on the right.",
402
+ "praxis.pageBuilder.studio.arrange.grid": "Three equal lanes for a simultaneous overview.",
403
+ "praxis.pageBuilder.studio.arrange.center": "A broad center between two supporting lanes.",
404
+ "praxis.pageBuilder.studio.arrange.wide-left": "Broad emphasis on the left, narrow support on the right.",
405
+ "praxis.pageBuilder.studio.arrange.wide-right": "Narrow support on the left, broad emphasis on the right.",
406
+ "praxis.pageBuilder.studio.arrange.four": "Four equal lanes for compact widgets.",
407
+ "praxis.pageBuilder.editor.arrange.secondary": "Right emphasis",
408
+ "praxis.pageBuilder.editor.arrange.center": "Center emphasis",
409
+ "praxis.pageBuilder.editor.arrange.wide-left": "Wide left",
410
+ "praxis.pageBuilder.editor.arrange.wide-right": "Wide right",
411
+ "praxis.pageBuilder.editor.arrange.four": "Four columns",
412
+ "praxis.pageBuilder.presets.inheritedLayout": "Inherited organization",
413
+ "praxis.pageBuilder.presets.roles": "Organization roles",
414
+ "praxis.pageBuilder.presets.slot.hero": "Highlight",
415
+ "praxis.pageBuilder.presets.slot.kpis": "Metrics",
416
+ "praxis.pageBuilder.presets.slot.charts": "Charts",
417
+ "praxis.pageBuilder.presets.slot.primary-chart": "Primary chart",
418
+ "praxis.pageBuilder.presets.slot.detail-table": "Detail table",
419
+ "praxis.pageBuilder.presets.slot.filters": "Filters",
420
+ "praxis.pageBuilder.presets.slot.table": "Main table",
421
+ "praxis.pageBuilder.presets.slot.aux": "Supporting charts",
422
+ "praxis.pageBuilder.presets.slot.master": "Master list",
423
+ "praxis.pageBuilder.presets.slot.detail-kpis": "Record metrics",
424
+ "praxis.pageBuilder.presets.slot.detail-tabs": "Tabbed details",
425
+ "praxis.pageBuilder.presets.slot.status-cards": "Status overview",
426
+ "praxis.pageBuilder.presets.slot.timelines": "Time series",
427
+ "praxis.pageBuilder.presets.slot.timeline-a": "Primary series",
428
+ "praxis.pageBuilder.presets.slot.queue": "Work queue",
429
+ "praxis.pageBuilder.presets.slot.alerts": "Alerts",
430
+ "praxis.pageBuilder.presets.scope": "Effect of this choice",
431
+ "praxis.pageBuilder.presets.canvas": "Canvas positions are preserved. This preset does not move or resize existing widgets.",
432
+ "praxis.pageBuilder.presets.flow": "Explicit layout and grouping take precedence over preset defaults. Selecting a preset does not create widgets or data connections.",
433
+ "praxis.pageBuilder.presets.effectiveTheme": "Effective page theme",
434
+ "praxis.pageBuilder.presets.hostTheme": "Host theme",
435
+ "praxis.pageBuilder.presets.explicit": "Explicit choice",
436
+ "praxis.pageBuilder.presets.inherited": "Inherited from layout",
437
+ "praxis.pageBuilder.presets.host": "No page theme override",
438
+ "praxis.pageBuilder.presets.suggested": "Suggested by the layout",
439
+ "praxis.pageBuilder.presets.notActive": "not active",
440
+ "praxis.pageBuilder.presets.compare": "Compare page organizations",
441
+ "praxis.pageBuilder.presets.diagramHelp": "Role diagrams, not a preview of your page. Compare the structure, then select an organization. Existing widgets and connections are preserved.",
442
+ "praxis.pageBuilder.presets.selected": "Selected",
443
+ "praxis.pageBuilder.presets.required": "required role",
444
+ "praxis.pageBuilder.presets.choose": "Choose",
445
+ "praxis.pageBuilder.presets.mobile": "Mobile preferences describe the intended composition. Verify the actual responsive layout; selection alone does not hide content, create tabs or connect master and detail.",
446
+ "praxis.pageBuilder.presets.declaredPolicy": "Declared preference, not an automatic transformation",
447
+ "praxis.pageBuilder.presets.layout.analytics-overview": "Analytics overview",
448
+ "praxis.pageBuilder.presets.layout.kpi-plus-table": "Metrics and table",
449
+ "praxis.pageBuilder.presets.layout.master-detail-dashboard": "Master and details",
450
+ "praxis.pageBuilder.presets.layout.ops-monitoring": "Operations monitoring",
451
+ "praxis.pageBuilder.presets.use.analytics-overview": "Compare metrics, trends and details across capacity, sales, costs and quality.",
452
+ "praxis.pageBuilder.presets.use.kpi-plus-table": "Review a central table supported by metrics, for orders, inventory and reconciliation.",
453
+ "praxis.pageBuilder.presets.use.master-detail-dashboard": "Investigate an item and its details, for customers, assets and cases. Selection and data connections must be configured.",
454
+ "praxis.pageBuilder.presets.use.ops-monitoring": "Follow status, trends, queues and alerts across services, production and logistics. Preserve access to alerts on mobile.",
364
455
  "praxis.pageBuilder.shell.availabilityUnknown": "Availability has not been checked for this widget. Validate the requirements before use.",
365
456
  "praxis.pageBuilder.shell.availabilityReady": "Available in this widget now.",
366
457
  "praxis.pageBuilder.shell.availabilityBlocked": "Unavailable in this widget now.",
@@ -413,9 +504,36 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
413
504
  "praxis.pageBuilder.shell.scopeToolbar": "Toolbar",
414
505
  "praxis.pageBuilder.shell.scopeSelection": "Selected item",
415
506
  "praxis.pageBuilder.shell.scopeGeneral": "General use",
507
+ "praxis.pageBuilder.editor.arrange.title": "Arrange existing widgets",
508
+ "praxis.pageBuilder.editor.arrange.help": "Base canvas, in current reading order. Inheriting devices follow the base; explicit device positions are preserved. Locked positions stay in place.",
509
+ "praxis.pageBuilder.editor.arrange.stack": "Work column",
510
+ "praxis.pageBuilder.editor.arrange.compare": "Two-column comparison",
511
+ "praxis.pageBuilder.editor.arrange.primary": "Two columns: ⅔ and ⅓",
512
+ "praxis.pageBuilder.editor.arrange.grid": "Three-column grid",
513
+ "praxis.pageBuilder.editor.arrange.affected": "Widgets to change",
514
+ "praxis.pageBuilder.editor.arrange.position": "Column / row",
515
+ "praxis.pageBuilder.editor.arrange.use": "Use arrangement in draft",
516
+ "praxis.pageBuilder.editor.arrange.undo": "Undo arrangement",
517
+ "praxis.pageBuilder.editor.arrange.geometry": "Use a complete canvas with valid positions and no overlaps. The grid must fit the chosen columns.",
518
+ "praxis.pageBuilder.editor.arrange.limits": "This arrangement conflicts with the indicated widget limits or locks. Choose another arrangement or review the limits.",
519
+ "praxis.pageBuilder.editor.arrange.device-overrides": "Partial device positions depend on this geometry. Review those positions before arranging the base.",
520
+ "praxis.pageBuilder.editor.arrange.unchanged": "This arrangement already matches the page.",
521
+ "praxis.pageBuilder.editor.arrange.stale": "The draft changed. Prepare the arrangement again.",
522
+ "praxis.pageBuilder.editor.arrange.staged": "Arrangement added to the draft. Use Apply to preview it on the page.",
523
+ "praxis.pageBuilder.editor.arrange.undone": "The previous arrangement is restored in the draft. Use Apply to update the page.",
416
524
  'praxis.pageBuilder.shell.sampleOnly': 'Illustrative data',
417
525
  'praxis.pageBuilder.shell.sampleMetricLabel': 'Active employees',
418
526
  'praxis.pageBuilder.shell.sampleMetricContext': '91% of planned workforce',
527
+ "praxis.pageBuilder.shell.cardFilter": "Backdrop filter",
528
+ "praxis.pageBuilder.shell.cardFilterHelp": "Use with a translucent background. Example: blur(16px) saturate(125%). Use none to disable.",
529
+ "praxis.pageBuilder.shell.cardFallbackBg": "Opaque fallback background",
530
+ "praxis.pageBuilder.shell.cardFallbackHelp": "Preserves readability in fullscreen, with reduced transparency or without backdrop support.",
531
+ "praxis.pageBuilder.shell.effectiveStyle": "Base style",
532
+ "praxis.pageBuilder.shell.styleFromWidget": "Chosen for this widget",
533
+ "praxis.pageBuilder.shell.styleFromPage": "Inherited from the page or theme",
534
+ "praxis.pageBuilder.shell.styleFromDefault": "Component default",
535
+ "praxis.pageBuilder.shell.adjustmentsWin": "adjustments override the style",
536
+ "praxis.pageBuilder.shell.surfaceDisabled": "Content-only presentation keeps the outer shell disabled.",
419
537
  'praxis.pageBuilder.shell.customAdjustments': 'Custom adjustments',
420
538
  'praxis.pageBuilder.shell.clearAdjustments': 'Use style only',
421
539
  'praxis.pageBuilder.connections.editor.flowLegend': 'Connection types',
@@ -514,6 +632,14 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
514
632
  "praxis.pageBuilder.shell.windowControls": "Window controls",
515
633
  "praxis.pageBuilder.shell.collapsible": "Allow collapsing",
516
634
  "praxis.pageBuilder.shell.cardBg": "Card background",
635
+ "praxis.pageBuilder.shell.cardBorderWidth": "Border width",
636
+ "praxis.pageBuilder.shell.cardBorderStyle": "Border style",
637
+ "praxis.pageBuilder.shell.headerBorderWidth": "Divider width",
638
+ "praxis.pageBuilder.shell.headerBorderStyle": "Divider style",
639
+ "praxis.pageBuilder.shell.cardBorderWidthHelp": "E.g. 2px. Per side: 1px 1px 1px 6px (top, right, bottom, left).",
640
+ "praxis.pageBuilder.shell.headerBorderWidthHelp": "E.g. 2px. Applies only to the bottom header divider.",
641
+ "praxis.pageBuilder.shell.cardBorderStyleHelp": "CSS styles: solid, dashed, dotted, double or none. Accepts per-side values.",
642
+ "praxis.pageBuilder.shell.headerBorderStyleHelp": "Divider style: solid, dashed, dotted, double or none.",
517
643
  "praxis.pageBuilder.shell.cardBorder": "Border color",
518
644
  "praxis.pageBuilder.shell.cardRadius": "Corner rounding",
519
645
  "praxis.pageBuilder.shell.cardShadow": "Shadow",
@@ -559,18 +685,6 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
559
685
  'praxis.pageBuilder.shell.inheritPagePreset': "Use the style defined on the page",
560
686
  'praxis.pageBuilder.shell.useDefaultAppearance': "Use the default widget appearance",
561
687
  'praxis.pageBuilder.shell.customPresetDescription': 'Style available on this page',
562
- 'praxis.widgetShell.presets.integratedCard.label': 'Integrated card',
563
- 'praxis.widgetShell.presets.integratedCard.description': 'One continuous surface for the header, actions and widget body.',
564
- 'praxis.widgetShell.presets.outlinedCard.label': 'Outlined card',
565
- 'praxis.widgetShell.presets.outlinedCard.description': 'A quiet continuous surface with a clear outer outline and no shadow.',
566
- 'praxis.widgetShell.presets.elevatedCard.label': 'Elevated card',
567
- 'praxis.widgetShell.presets.elevatedCard.description': 'A continuous surface separated from the page by a soft shadow.',
568
- 'praxis.widgetShell.presets.tonalCard.label': 'Tonal card',
569
- 'praxis.widgetShell.presets.tonalCard.description': 'A softly tinted continuous surface with minimal elevation.',
570
- 'praxis.widgetShell.presets.sectionedCard.label': 'Sectioned card',
571
- 'praxis.widgetShell.presets.sectionedCard.description': 'A distinct header layer for dense operational content.',
572
- 'praxis.widgetShell.presets.frameless.label': 'Frameless',
573
- 'praxis.widgetShell.presets.frameless.description': 'No outer surface, outline or shadow; keeps the content reading inset.',
574
688
  'praxis.pageBuilder.authoringSource.alert.title': 'Semantic lineage requires attention',
575
689
  'praxis.pageBuilder.authoringSource.alert.description': 'The page remains available, but this source will not be reused until new governed authoring restores its provenance.',
576
690
  'praxis.pageBuilder.authoringSource.diagnostics.authoring-source-schema-invalid': 'The authoring envelope does not match the schema supported by Page Builder.',
@@ -624,6 +738,7 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
624
738
  'praxis.pageBuilder.editor.layout.none': 'None',
625
739
  'praxis.pageBuilder.editor.layout.themePreset': 'Theme preset',
626
740
  'praxis.pageBuilder.editor.layout.layoutDefault': 'Layout default',
741
+ 'praxis.pageBuilder.editor.layout.inheritShell': 'Inherit appearance',
627
742
  'praxis.pageBuilder.editor.layout.shellPreset': 'Shell visual preset',
628
743
  'praxis.pageBuilder.editor.grouping.section': 'Grouping',
629
744
  'praxis.pageBuilder.editor.grouping.title': 'Grouping',
@@ -1309,6 +1424,97 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
1309
1424
  };
1310
1425
 
1311
1426
  const PRAXIS_PAGE_BUILDER_PT_BR = {
1427
+ "praxis.pageBuilder.studio.gridSize": "Tamanho em colunas × linhas",
1428
+ "praxis.pageBuilder.studio.visualTab": "Templates visuais",
1429
+ "praxis.pageBuilder.studio.layoutTab": "Templates de layout",
1430
+ "praxis.pageBuilder.studio.arrangeTab": "Reorganizar",
1431
+ "praxis.pageBuilder.studio.settingsTab": "Ajustes",
1432
+ "praxis.pageBuilder.studio.visualEyebrow": "Aparência",
1433
+ "praxis.pageBuilder.studio.visualTitle": "Dê uma linguagem visual à página",
1434
+ "praxis.pageBuilder.studio.visualHelp": "Escolha superfícies, densidade e estilo dos gráficos em conjunto. Personalizações de cada widget continuam tendo prioridade.",
1435
+ "praxis.pageBuilder.studio.layoutEyebrow": "Modelo de composição",
1436
+ "praxis.pageBuilder.studio.layoutTitle": "Escolha a estrutura para seu objetivo",
1437
+ "praxis.pageBuilder.studio.layoutHelp": "Compare os papéis e o tema padrão de cada modelo. Para redistribuir os widgets deste canvas, abra Reorganizar.",
1438
+ "praxis.pageBuilder.studio.inheritLayout": "Usar modelo herdado",
1439
+ "praxis.pageBuilder.studio.goArrange": "Reorganizar widgets existentes",
1440
+ "praxis.pageBuilder.studio.arrangeEyebrow": "Widgets existentes",
1441
+ "praxis.pageBuilder.studio.arrangeTitle": "Encontre o equilíbrio entre os espaços",
1442
+ "praxis.pageBuilder.studio.arrangeHelp": "Escolha uma distribuição, compare as posições reais e use no rascunho. Os widgets mantêm a ordem de leitura e o conteúdo.",
1443
+ "praxis.pageBuilder.studio.samplesHelp": "Amostras ilustrativas com os tokens do preset. Aplique para ver o resultado com seu conteúdo.",
1444
+ "praxis.pageBuilder.studio.shellTitle": "Personalizar superfícies dos widgets",
1445
+ "praxis.pageBuilder.studio.shellHelp": "Substitua a superfície padrão do tema da página. Aparências definidas em cada widget mantêm a prioridade.",
1446
+ "praxis.pageBuilder.studio.density.compact": "Compacto",
1447
+ "praxis.pageBuilder.studio.density.comfortable": "Confortável",
1448
+ "praxis.pageBuilder.studio.moreColumns": "Requer mais colunas na grade",
1449
+ "praxis.pageBuilder.studio.reviewTitle": "Compare antes de usar",
1450
+ "praxis.pageBuilder.studio.changedWidgets": "widgets mudam",
1451
+ "praxis.pageBuilder.studio.before": "Antes",
1452
+ "praxis.pageBuilder.studio.after": "Depois",
1453
+ "praxis.pageBuilder.studio.widgetDetails": "Posições e tamanhos dos widgets",
1454
+ "praxis.pageBuilder.studio.mapHelp": "Mapa numerado dos seus widgets. Posições e extensões estão em escala; a altura do conteúdo é avaliada na página.",
1455
+ "praxis.pageBuilder.studio.rules": "Escopo, dispositivos e widgets bloqueados",
1456
+ "praxis.pageBuilder.studio.goLimits": "Revisar limites dos widgets",
1457
+ "praxis.pageBuilder.studio.theme.analytics-calm": "Leitura analítica com superfícies leves e espaço confortável.",
1458
+ "praxis.pageBuilder.studio.theme.workspace-balanced": "Equilíbrio para rotinas de trabalho e consulta de dados.",
1459
+ "praxis.pageBuilder.studio.theme.ops-monitoring": "Maior densidade e destaque para status, filas e alertas.",
1460
+ "praxis.pageBuilder.studio.theme.executive-command-center": "Hierarquia compacta para visão executiva e decisões.",
1461
+ "praxis.pageBuilder.studio.arrange.stack": "Uma faixa por widget, para leitura sequencial.",
1462
+ "praxis.pageBuilder.studio.arrange.compare": "Duas faixas iguais para comparar informações.",
1463
+ "praxis.pageBuilder.studio.arrange.primary": "Mais espaço à esquerda, com apoio à direita.",
1464
+ "praxis.pageBuilder.studio.arrange.secondary": "Apoio à esquerda e mais espaço à direita.",
1465
+ "praxis.pageBuilder.studio.arrange.grid": "Três faixas iguais para uma visão simultânea.",
1466
+ "praxis.pageBuilder.studio.arrange.center": "Centro amplo entre duas faixas de apoio.",
1467
+ "praxis.pageBuilder.studio.arrange.wide-left": "Destaque amplo à esquerda, apoio estreito à direita.",
1468
+ "praxis.pageBuilder.studio.arrange.wide-right": "Apoio estreito à esquerda, destaque amplo à direita.",
1469
+ "praxis.pageBuilder.studio.arrange.four": "Quatro faixas iguais para widgets compactos.",
1470
+ "praxis.pageBuilder.editor.arrange.secondary": "Destaque à direita",
1471
+ "praxis.pageBuilder.editor.arrange.center": "Centro em destaque",
1472
+ "praxis.pageBuilder.editor.arrange.wide-left": "Panorama à esquerda",
1473
+ "praxis.pageBuilder.editor.arrange.wide-right": "Panorama à direita",
1474
+ "praxis.pageBuilder.editor.arrange.four": "Quatro colunas",
1475
+ "praxis.pageBuilder.presets.inheritedLayout": "Organização herdada",
1476
+ "praxis.pageBuilder.presets.roles": "Papéis da organização",
1477
+ "praxis.pageBuilder.presets.slot.hero": "Destaque",
1478
+ "praxis.pageBuilder.presets.slot.kpis": "Indicadores",
1479
+ "praxis.pageBuilder.presets.slot.charts": "Gráficos",
1480
+ "praxis.pageBuilder.presets.slot.primary-chart": "Gráfico principal",
1481
+ "praxis.pageBuilder.presets.slot.detail-table": "Tabela de detalhes",
1482
+ "praxis.pageBuilder.presets.slot.filters": "Filtros",
1483
+ "praxis.pageBuilder.presets.slot.table": "Tabela central",
1484
+ "praxis.pageBuilder.presets.slot.aux": "Gráficos auxiliares",
1485
+ "praxis.pageBuilder.presets.slot.master": "Lista principal",
1486
+ "praxis.pageBuilder.presets.slot.detail-kpis": "Indicadores do registro",
1487
+ "praxis.pageBuilder.presets.slot.detail-tabs": "Detalhes em abas",
1488
+ "praxis.pageBuilder.presets.slot.status-cards": "Situação geral",
1489
+ "praxis.pageBuilder.presets.slot.timelines": "Séries temporais",
1490
+ "praxis.pageBuilder.presets.slot.timeline-a": "Série principal",
1491
+ "praxis.pageBuilder.presets.slot.queue": "Fila de trabalho",
1492
+ "praxis.pageBuilder.presets.slot.alerts": "Alertas",
1493
+ "praxis.pageBuilder.presets.scope": "Efeito desta escolha",
1494
+ "praxis.pageBuilder.presets.canvas": "As posições do canvas são preservadas. Este preset não move nem redimensiona os widgets existentes.",
1495
+ "praxis.pageBuilder.presets.flow": "Layout e agrupamento explícitos prevalecem sobre os padrões do preset. Escolher uma organização não cria widgets nem conexões de dados.",
1496
+ "praxis.pageBuilder.presets.effectiveTheme": "Tema efetivo da página",
1497
+ "praxis.pageBuilder.presets.hostTheme": "Tema do aplicativo",
1498
+ "praxis.pageBuilder.presets.explicit": "Escolha explícita",
1499
+ "praxis.pageBuilder.presets.inherited": "Herdado da organização",
1500
+ "praxis.pageBuilder.presets.host": "Sem tema próprio da página",
1501
+ "praxis.pageBuilder.presets.suggested": "Tema sugerido pela organização",
1502
+ "praxis.pageBuilder.presets.notActive": "não ativo",
1503
+ "praxis.pageBuilder.presets.compare": "Comparar organizações da página",
1504
+ "praxis.pageBuilder.presets.diagramHelp": "Esquemas de papéis, não uma prévia da sua página. Compare a estrutura e escolha uma organização. Widgets e conexões existentes são preservados.",
1505
+ "praxis.pageBuilder.presets.selected": "Selecionado",
1506
+ "praxis.pageBuilder.presets.required": "papel obrigatório",
1507
+ "praxis.pageBuilder.presets.choose": "Escolher",
1508
+ "praxis.pageBuilder.presets.mobile": "As preferências mobile descrevem a intenção da composição. Confira o layout responsivo real: escolher um preset não oculta conteúdo, cria abas ou conecta principal e detalhe automaticamente.",
1509
+ "praxis.pageBuilder.presets.declaredPolicy": "Preferência declarada, não uma transformação automática",
1510
+ "praxis.pageBuilder.presets.layout.analytics-overview": "Visão analítica",
1511
+ "praxis.pageBuilder.presets.layout.kpi-plus-table": "Indicadores e tabela",
1512
+ "praxis.pageBuilder.presets.layout.master-detail-dashboard": "Principal e detalhes",
1513
+ "praxis.pageBuilder.presets.layout.ops-monitoring": "Monitoramento operacional",
1514
+ "praxis.pageBuilder.presets.use.analytics-overview": "Visão geral para comparar indicadores, tendências e detalhes. Útil em capacidade, vendas, custos e qualidade.",
1515
+ "praxis.pageBuilder.presets.use.kpi-plus-table": "Consulta e conferência com uma tabela central apoiada por métricas. Útil em pedidos, estoque e conciliação.",
1516
+ "praxis.pageBuilder.presets.use.master-detail-dashboard": "Investigar um item e seus detalhes. Útil em clientes, ativos e processos; a seleção e os vínculos precisam estar configurados.",
1517
+ "praxis.pageBuilder.presets.use.ops-monitoring": "Acompanhar status, séries, filas e alertas. Útil em serviços, produção e logística; preserve acesso aos alertas no mobile.",
1312
1518
  "praxis.pageBuilder.shell.availabilityUnknown": "Disponibilidade não verificada neste widget. Valide os requisitos antes de usar.",
1313
1519
  "praxis.pageBuilder.shell.availabilityReady": "Disponível neste widget agora.",
1314
1520
  "praxis.pageBuilder.shell.availabilityBlocked": "Indisponível neste widget agora.",
@@ -1361,9 +1567,36 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
1361
1567
  "praxis.pageBuilder.shell.scopeToolbar": "Barra de ferramentas",
1362
1568
  "praxis.pageBuilder.shell.scopeSelection": "Item selecionado",
1363
1569
  "praxis.pageBuilder.shell.scopeGeneral": "Uso geral",
1570
+ "praxis.pageBuilder.editor.arrange.title": "Reorganizar widgets existentes",
1571
+ "praxis.pageBuilder.editor.arrange.help": "Canvas base, na ordem atual de leitura. Dispositivos que herdam acompanham a base; posições explícitas por dispositivo são preservadas. Posições travadas permanecem no lugar.",
1572
+ "praxis.pageBuilder.editor.arrange.stack": "Coluna de trabalho",
1573
+ "praxis.pageBuilder.editor.arrange.compare": "Comparação em duas colunas",
1574
+ "praxis.pageBuilder.editor.arrange.primary": "Duas colunas: ⅔ e ⅓",
1575
+ "praxis.pageBuilder.editor.arrange.grid": "Grade de três colunas",
1576
+ "praxis.pageBuilder.editor.arrange.affected": "Widgets que serão alterados",
1577
+ "praxis.pageBuilder.editor.arrange.position": "Coluna / linha",
1578
+ "praxis.pageBuilder.editor.arrange.use": "Usar organização no rascunho",
1579
+ "praxis.pageBuilder.editor.arrange.undo": "Desfazer organização",
1580
+ "praxis.pageBuilder.editor.arrange.geometry": "Use um canvas completo, com posições válidas e sem sobreposições. A grade precisa comportar as colunas escolhidas.",
1581
+ "praxis.pageBuilder.editor.arrange.limits": "Esta organização não respeita os limites ou travas dos widgets indicados. Escolha outra disposição ou revise os limites.",
1582
+ "praxis.pageBuilder.editor.arrange.device-overrides": "Há posições parciais por dispositivo que dependem desta geometria. Revise essas posições antes de reorganizar a base.",
1583
+ "praxis.pageBuilder.editor.arrange.unchanged": "Esta organização já corresponde à página.",
1584
+ "praxis.pageBuilder.editor.arrange.stale": "O rascunho mudou. Prepare a organização novamente.",
1585
+ "praxis.pageBuilder.editor.arrange.staged": "Organização adicionada ao rascunho. Use Aplicar para visualizá-la na página.",
1586
+ "praxis.pageBuilder.editor.arrange.undone": "A organização anterior foi restaurada no rascunho. Use Aplicar para atualizar a página.",
1364
1587
  'praxis.pageBuilder.shell.sampleOnly': 'Dados ilustrativos',
1365
1588
  'praxis.pageBuilder.shell.sampleMetricLabel': 'Funcionários ativos',
1366
1589
  'praxis.pageBuilder.shell.sampleMetricContext': '91% da força planejada',
1590
+ "praxis.pageBuilder.shell.cardFilter": "Desfoque do fundo",
1591
+ "praxis.pageBuilder.shell.cardFilterHelp": "Use com fundo translúcido. Exemplo: blur(16px) saturate(125%). Use none para desativar.",
1592
+ "praxis.pageBuilder.shell.cardFallbackBg": "Fundo opaco de alternativa",
1593
+ "praxis.pageBuilder.shell.cardFallbackHelp": "Preserva a leitura em tela cheia, com transparência reduzida ou sem suporte ao desfoque.",
1594
+ "praxis.pageBuilder.shell.effectiveStyle": "Estilo base",
1595
+ "praxis.pageBuilder.shell.styleFromWidget": "Escolhido neste widget",
1596
+ "praxis.pageBuilder.shell.styleFromPage": "Herdado da página ou do tema",
1597
+ "praxis.pageBuilder.shell.styleFromDefault": "Padrão do componente",
1598
+ "praxis.pageBuilder.shell.adjustmentsWin": "os ajustes prevalecem sobre o estilo",
1599
+ "praxis.pageBuilder.shell.surfaceDisabled": "A apresentação somente conteúdo mantém a moldura desativada.",
1367
1600
  'praxis.pageBuilder.shell.customAdjustments': 'Ajustes personalizados',
1368
1601
  'praxis.pageBuilder.shell.clearAdjustments': 'Usar apenas o estilo',
1369
1602
  'praxis.pageBuilder.connections.editor.flowLegend': 'Tipos de conexão',
@@ -1453,6 +1686,14 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
1453
1686
  "praxis.pageBuilder.shell.windowControls": "Controles de janela",
1454
1687
  "praxis.pageBuilder.shell.collapsible": "Permitir recolher",
1455
1688
  "praxis.pageBuilder.shell.cardBg": "Fundo do card",
1689
+ "praxis.pageBuilder.shell.cardBorderWidth": "Espessura da borda",
1690
+ "praxis.pageBuilder.shell.cardBorderStyle": "Traço da borda",
1691
+ "praxis.pageBuilder.shell.headerBorderWidth": "Espessura do divisor",
1692
+ "praxis.pageBuilder.shell.headerBorderStyle": "Traço do divisor",
1693
+ "praxis.pageBuilder.shell.cardBorderWidthHelp": "Ex.: 2px. Para variar por lado: 1px 1px 1px 6px (superior, direita, inferior, esquerda).",
1694
+ "praxis.pageBuilder.shell.headerBorderWidthHelp": "Ex.: 2px. Afeta somente o divisor inferior do cabeçalho.",
1695
+ "praxis.pageBuilder.shell.cardBorderStyleHelp": "Traços CSS: solid, dashed, dotted, double ou none. Aceita um valor por lado.",
1696
+ "praxis.pageBuilder.shell.headerBorderStyleHelp": "Traço do divisor: solid, dashed, dotted, double ou none.",
1456
1697
  "praxis.pageBuilder.shell.cardBorder": "Cor da borda",
1457
1698
  "praxis.pageBuilder.shell.cardRadius": "Arredondamento dos cantos",
1458
1699
  "praxis.pageBuilder.shell.cardShadow": "Sombra",
@@ -1507,18 +1748,6 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
1507
1748
  'praxis.pageBuilder.shell.inheritPagePreset': "Usar o estilo definido na página",
1508
1749
  'praxis.pageBuilder.shell.useDefaultAppearance': "Usar a aparência padrão do widget",
1509
1750
  'praxis.pageBuilder.shell.customPresetDescription': "Estilo disponibilizado nesta página",
1510
- 'praxis.widgetShell.presets.integratedCard.label': 'Card integrado',
1511
- 'praxis.widgetShell.presets.integratedCard.description': 'Uma superfície contínua para cabeçalho, ações e corpo do widget.',
1512
- 'praxis.widgetShell.presets.outlinedCard.label': 'Card contornado',
1513
- 'praxis.widgetShell.presets.outlinedCard.description': 'Superfície contínua e discreta, com contorno externo e sem sombra.',
1514
- 'praxis.widgetShell.presets.elevatedCard.label': 'Card elevado',
1515
- 'praxis.widgetShell.presets.elevatedCard.description': 'Superfície contínua separada da página por uma sombra suave.',
1516
- 'praxis.widgetShell.presets.tonalCard.label': 'Card tonal',
1517
- 'praxis.widgetShell.presets.tonalCard.description': 'Superfície contínua levemente tonalizada e com elevação mínima.',
1518
- 'praxis.widgetShell.presets.sectionedCard.label': 'Card seccionado',
1519
- 'praxis.widgetShell.presets.sectionedCard.description': 'Cabeçalho destacado para conteúdos operacionais densos.',
1520
- 'praxis.widgetShell.presets.frameless.label': 'Sem moldura',
1521
- 'praxis.widgetShell.presets.frameless.description': 'Sem superfície externa, contorno ou sombra; mantém o recuo de leitura do conteúdo.',
1522
1751
  'praxis.pageBuilder.authoringSource.alert.title': 'Linhagem semântica requer atenção',
1523
1752
  'praxis.pageBuilder.authoringSource.alert.description': 'A página continua disponível, mas esta fonte não será reutilizada até que uma nova autoria governada restabeleça sua proveniência.',
1524
1753
  'praxis.pageBuilder.authoringSource.diagnostics.authoring-source-schema-invalid': 'O envelope de autoria não atende ao schema suportado pelo Page Builder.',
@@ -1572,6 +1801,7 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
1572
1801
  'praxis.pageBuilder.editor.layout.none': 'Nenhum',
1573
1802
  'praxis.pageBuilder.editor.layout.themePreset': 'Predefinição de tema',
1574
1803
  'praxis.pageBuilder.editor.layout.layoutDefault': 'Padrão do layout',
1804
+ 'praxis.pageBuilder.editor.layout.inheritShell': 'Herdar aparência',
1575
1805
  'praxis.pageBuilder.editor.layout.shellPreset': 'Predefinição visual da shell',
1576
1806
  'praxis.pageBuilder.editor.grouping.section': 'Agrupamento',
1577
1807
  'praxis.pageBuilder.editor.grouping.title': 'Agrupamento',
@@ -2933,9 +3163,10 @@ class WidgetShellEditorComponent {
2933
3163
  }
2934
3164
  hasAppearanceErrors() {
2935
3165
  return [
2936
- this.form.controls.cardBg, this.form.controls.cardBorder,
3166
+ this.form.controls.cardBg, this.form.controls.cardBorder, this.form.controls.cardBorderWidth, this.form.controls.cardBorderStyle,
2937
3167
  this.form.controls.cardRadius, this.form.controls.cardShadow,
2938
- this.form.controls.headerBg, this.form.controls.headerBorder,
3168
+ this.form.controls.cardFilter, this.form.controls.cardFallbackBg,
3169
+ this.form.controls.headerBg, this.form.controls.headerBorder, this.form.controls.headerBorderWidth, this.form.controls.headerBorderStyle,
2939
3170
  this.form.controls.titleColor, this.form.controls.subtitleColor,
2940
3171
  this.form.controls.iconColor, this.form.controls.bodyBg,
2941
3172
  this.form.controls.bodyColor, this.form.controls.bodyPadding,
@@ -2969,10 +3200,16 @@ class WidgetShellEditorComponent {
2969
3200
  collapsible: new FormControl(true, { nonNullable: true }),
2970
3201
  cardBg: new FormControl('', { nonNullable: true }),
2971
3202
  cardBorder: new FormControl('', { nonNullable: true }),
3203
+ cardBorderWidth: new FormControl('', { nonNullable: true }),
3204
+ cardBorderStyle: new FormControl('', { nonNullable: true }),
2972
3205
  cardRadius: new FormControl('', { nonNullable: true }),
2973
3206
  cardShadow: new FormControl('', { nonNullable: true }),
3207
+ cardFilter: new FormControl('', { nonNullable: true }),
3208
+ cardFallbackBg: new FormControl('', { nonNullable: true }),
2974
3209
  headerBg: new FormControl('', { nonNullable: true }),
2975
3210
  headerBorder: new FormControl('', { nonNullable: true }),
3211
+ headerBorderWidth: new FormControl('', { nonNullable: true }),
3212
+ headerBorderStyle: new FormControl('', { nonNullable: true }),
2976
3213
  titleColor: new FormControl('', { nonNullable: true }),
2977
3214
  subtitleColor: new FormControl('', { nonNullable: true }),
2978
3215
  iconColor: new FormControl('', { nonNullable: true }),
@@ -3022,8 +3259,8 @@ class WidgetShellEditorComponent {
3022
3259
  this.getActionAvailability = data?.getActionAvailability;
3023
3260
  if (this.canEditShellAppearance()) {
3024
3261
  const properties = {
3025
- cardBg: 'background', cardBorder: 'border-color', cardRadius: 'border-radius',
3026
- cardShadow: 'box-shadow', headerBg: 'background', headerBorder: 'border-color',
3262
+ cardBg: 'background', cardBorder: 'border-color', cardBorderWidth: 'border-width', cardBorderStyle: 'border-style', cardRadius: 'border-radius',
3263
+ cardShadow: 'box-shadow', cardFilter: 'backdrop-filter', cardFallbackBg: 'background', headerBg: 'background', headerBorder: 'border-bottom-color', headerBorderWidth: 'border-bottom-width', headerBorderStyle: 'border-bottom-style',
3027
3264
  titleColor: 'color', subtitleColor: 'color', iconColor: 'color',
3028
3265
  bodyBg: 'background', bodyColor: 'color', bodyPadding: 'padding',
3029
3266
  titleSize: 'font-size', titleWeight: 'font-weight', subtitleSize: 'font-size',
@@ -3083,6 +3320,11 @@ class WidgetShellEditorComponent {
3083
3320
  presetDescription(preset) {
3084
3321
  return this.i18n.resolve(preset.description, '');
3085
3322
  }
3323
+ effectivePresetLabel() {
3324
+ const id = this.form.controls.preset.value || this.pagePreset;
3325
+ const preset = this.presetOptions.find(option => option.id === id);
3326
+ return preset ? this.presetLabel(preset) : id || this.noPresetLabel();
3327
+ }
3086
3328
  presetTitle() {
3087
3329
  return this.i18n.t('praxis.pageBuilder.shell.presetTitle', undefined, 'Ready-made styles');
3088
3330
  }
@@ -3178,10 +3420,16 @@ class WidgetShellEditorComponent {
3178
3420
  collapsible: window.collapsible !== false,
3179
3421
  cardBg: card.background || '',
3180
3422
  cardBorder: card.borderColor || '',
3423
+ cardBorderWidth: card.borderWidth || '',
3424
+ cardBorderStyle: card.borderStyle || '',
3181
3425
  cardRadius: card.borderRadius || '',
3182
3426
  cardShadow: card.shadow || '',
3427
+ cardFilter: card.backdropFilter || '',
3428
+ cardFallbackBg: card.fallbackBackground || '',
3183
3429
  headerBg: header.background || '',
3184
3430
  headerBorder: header.borderColor || '',
3431
+ headerBorderWidth: header.borderWidth || '',
3432
+ headerBorderStyle: header.borderStyle || '',
3185
3433
  titleColor: header.titleColor || '',
3186
3434
  subtitleColor: header.subtitleColor || '',
3187
3435
  iconColor: header.iconColor || '',
@@ -3343,35 +3591,47 @@ class WidgetShellEditorComponent {
3343
3591
  const header = {};
3344
3592
  const body = {};
3345
3593
  const typography = {};
3346
- if (raw.cardBg)
3594
+ if (raw.cardBg.trim())
3347
3595
  card.background = raw.cardBg.trim();
3348
- if (raw.cardBorder)
3596
+ if (raw.cardBorder.trim())
3349
3597
  card.borderColor = raw.cardBorder.trim();
3350
- if (raw.cardRadius)
3598
+ if (raw.cardBorderWidth.trim())
3599
+ card.borderWidth = raw.cardBorderWidth.trim();
3600
+ if (raw.cardBorderStyle.trim())
3601
+ card.borderStyle = raw.cardBorderStyle.trim();
3602
+ if (raw.cardRadius.trim())
3351
3603
  card.borderRadius = raw.cardRadius.trim();
3352
- if (raw.cardShadow)
3604
+ if (raw.cardShadow.trim())
3353
3605
  card.shadow = raw.cardShadow.trim();
3354
- if (raw.headerBg)
3606
+ if (raw.cardFilter.trim())
3607
+ card.backdropFilter = raw.cardFilter.trim();
3608
+ if (raw.cardFallbackBg.trim())
3609
+ card.fallbackBackground = raw.cardFallbackBg.trim();
3610
+ if (raw.headerBg.trim())
3355
3611
  header.background = raw.headerBg.trim();
3356
- if (raw.headerBorder)
3612
+ if (raw.headerBorder.trim())
3357
3613
  header.borderColor = raw.headerBorder.trim();
3358
- if (raw.titleColor)
3614
+ if (raw.headerBorderWidth.trim())
3615
+ header.borderWidth = raw.headerBorderWidth.trim();
3616
+ if (raw.headerBorderStyle.trim())
3617
+ header.borderStyle = raw.headerBorderStyle.trim();
3618
+ if (raw.titleColor.trim())
3359
3619
  header.titleColor = raw.titleColor.trim();
3360
- if (raw.subtitleColor)
3620
+ if (raw.subtitleColor.trim())
3361
3621
  header.subtitleColor = raw.subtitleColor.trim();
3362
- if (raw.iconColor)
3622
+ if (raw.iconColor.trim())
3363
3623
  header.iconColor = raw.iconColor.trim();
3364
- if (raw.bodyBg)
3624
+ if (raw.bodyBg.trim())
3365
3625
  body.background = raw.bodyBg.trim();
3366
- if (raw.bodyColor)
3626
+ if (raw.bodyColor.trim())
3367
3627
  body.textColor = raw.bodyColor.trim();
3368
- if (raw.bodyPadding)
3628
+ if (raw.bodyPadding.trim())
3369
3629
  body.padding = raw.bodyPadding.trim();
3370
- if (raw.titleSize)
3630
+ if (raw.titleSize.trim())
3371
3631
  typography.titleSize = raw.titleSize.trim();
3372
- if (raw.titleWeight)
3632
+ if (raw.titleWeight.trim())
3373
3633
  typography.titleWeight = raw.titleWeight.trim();
3374
- if (raw.subtitleSize)
3634
+ if (raw.subtitleSize.trim())
3375
3635
  typography.subtitleSize = raw.subtitleSize.trim();
3376
3636
  const appearance = {};
3377
3637
  if (Object.keys(card).length)
@@ -3403,7 +3663,7 @@ class WidgetShellEditorComponent {
3403
3663
  return shellCapabilities[capability] !== false;
3404
3664
  }
3405
3665
  appearanceControls = [
3406
- 'cardBg', 'cardBorder', 'cardRadius', 'cardShadow', 'headerBg', 'headerBorder',
3666
+ 'cardBg', 'cardBorder', 'cardBorderWidth', 'cardBorderStyle', 'cardRadius', 'cardShadow', 'cardFilter', 'cardFallbackBg', 'headerBg', 'headerBorder', 'headerBorderWidth', 'headerBorderStyle',
3407
3667
  'titleColor', 'subtitleColor', 'iconColor', 'bodyBg', 'bodyColor', 'bodyPadding',
3408
3668
  'titleSize', 'titleWeight', 'subtitleSize',
3409
3669
  ];
@@ -3757,6 +4017,12 @@ class WidgetShellEditorComponent {
3757
4017
  <div class="shell-preset-field shell-field--wide">
3758
4018
  <span class="shell-preset-label">{{ presetTitle() }}</span>
3759
4019
  <p class="shell-hint">{{ shellText('presetHint') }}</p>
4020
+ <p class="shell-effective-style" role="status" data-testid="widget-shell-effective-style">
4021
+ <strong>{{ shellText('effectiveStyle') }}: {{ effectivePresetLabel() }}</strong>
4022
+ <span>{{ shellText(form.controls.preset.value ? 'styleFromWidget' : pagePreset ? 'styleFromPage' : 'styleFromDefault') }}</span>
4023
+ @if (appearanceOverrideCount()) { <span>{{ shellText('customAdjustments') }}: {{ appearanceOverrideCount() }} · {{ shellText('adjustmentsWin') }}</span> }
4024
+ @if (form.controls.kind.value === 'none') { <span>{{ shellText('surfaceDisabled') }}</span> }
4025
+ </p>
3760
4026
  <div class="shell-preset-grid" role="group" [attr.aria-label]="presetTitle()">
3761
4027
  <button
3762
4028
  type="button"
@@ -3782,8 +4048,13 @@ class WidgetShellEditorComponent {
3782
4048
  <span
3783
4049
  class="shell-preset-preview"
3784
4050
  aria-hidden="true"
3785
- [style.background]="p.appearance?.card?.background || null"
4051
+ [class.shell-preset-preview--backdrop]="!!p.appearance?.card?.backdropFilter && p.appearance?.card?.backdropFilter !== 'none'"
4052
+ [style.--shell-mini-bg]="p.appearance?.card?.background || null"
4053
+ [style.--shell-mini-fallback]="p.appearance?.card?.fallbackBackground || null"
4054
+ [style.--shell-mini-filter]="p.appearance?.card?.backdropFilter || null"
3786
4055
  [style.border-color]="p.appearance?.card?.borderColor || null"
4056
+ [style.border-width]="p.appearance?.card?.borderWidth || null"
4057
+ [style.border-style]="p.appearance?.card?.borderStyle || null"
3787
4058
  [style.border-radius]="p.appearance?.card?.borderRadius || null"
3788
4059
  [style.box-shadow]="p.appearance?.card?.shadow || null"
3789
4060
  >
@@ -3791,6 +4062,8 @@ class WidgetShellEditorComponent {
3791
4062
  class="shell-preset-preview__header"
3792
4063
  [style.background]="p.appearance?.header?.background || null"
3793
4064
  [style.border-color]="p.appearance?.header?.borderColor || null"
4065
+ [style.border-bottom-width]="p.appearance?.header?.borderWidth || null"
4066
+ [style.border-bottom-style]="p.appearance?.header?.borderStyle || null"
3794
4067
  ><span class="shell-mini-title" [style.background]="p.appearance?.header?.titleColor || null"></span><span class="shell-mini-action" [style.background]="p.appearance?.header?.iconColor || null"></span></span>
3795
4068
  <span class="shell-preset-preview__body" [style.background]="p.appearance?.body?.background || null" [style.color]="p.appearance?.body?.textColor || null">
3796
4069
  <span class="shell-mini-value"></span>
@@ -3916,6 +4189,18 @@ class WidgetShellEditorComponent {
3916
4189
  <h3 class="shell-section">{{ shellText('advancedAppearance') }}</h3>
3917
4190
  <p class="shell-hint">{{ shellText('advancedHint') }}</p>
3918
4191
  <div class="shell-grid">
4192
+ <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4193
+ <mat-label>{{ shellText('cardFilter') }}</mat-label>
4194
+ <input matInput [formControl]="form.controls.cardFilter" placeholder="blur(16px) saturate(125%)" />
4195
+ <button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardFilterHelp')" [attr.aria-label]="shellText('cardFilterHelp')"></button>
4196
+ @if (form.controls.cardFilter.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
4197
+ </mat-form-field>
4198
+ <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4199
+ <mat-label>{{ shellText('cardFallbackBg') }}</mat-label>
4200
+ <input matInput [formControl]="form.controls.cardFallbackBg" />
4201
+ <button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardFallbackHelp')" [attr.aria-label]="shellText('cardFallbackHelp')"></button>
4202
+ @if (form.controls.cardFallbackBg.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
4203
+ </mat-form-field>
3919
4204
  <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
3920
4205
  <mat-label>{{ shellText('cardBg') }}</mat-label>
3921
4206
  <input
@@ -3938,6 +4223,18 @@ class WidgetShellEditorComponent {
3938
4223
  <mat-error>{{ appearanceError() }}</mat-error>
3939
4224
  }
3940
4225
  </mat-form-field>
4226
+ <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4227
+ <mat-label>{{ shellText('cardBorderWidth') }}</mat-label>
4228
+ <input matInput [formControl]="form.controls.cardBorderWidth" placeholder="2px" />
4229
+ <button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardBorderWidthHelp')" [attr.aria-label]="shellText('cardBorderWidthHelp')"></button>
4230
+ @if (form.controls.cardBorderWidth.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
4231
+ </mat-form-field>
4232
+ <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4233
+ <mat-label>{{ shellText('cardBorderStyle') }}</mat-label>
4234
+ <input matInput [formControl]="form.controls.cardBorderStyle" placeholder="solid" />
4235
+ <button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardBorderStyleHelp')" [attr.aria-label]="shellText('cardBorderStyleHelp')"></button>
4236
+ @if (form.controls.cardBorderStyle.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
4237
+ </mat-form-field>
3941
4238
  <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
3942
4239
  <mat-label>{{ shellText('cardRadius') }}</mat-label>
3943
4240
  <input
@@ -3982,6 +4279,18 @@ class WidgetShellEditorComponent {
3982
4279
  <mat-error>{{ appearanceError() }}</mat-error>
3983
4280
  }
3984
4281
  </mat-form-field>
4282
+ <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4283
+ <mat-label>{{ shellText('headerBorderWidth') }}</mat-label>
4284
+ <input matInput [formControl]="form.controls.headerBorderWidth" placeholder="2px" />
4285
+ <button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('headerBorderWidthHelp')" [attr.aria-label]="shellText('headerBorderWidthHelp')"></button>
4286
+ @if (form.controls.headerBorderWidth.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
4287
+ </mat-form-field>
4288
+ <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4289
+ <mat-label>{{ shellText('headerBorderStyle') }}</mat-label>
4290
+ <input matInput [formControl]="form.controls.headerBorderStyle" placeholder="solid" />
4291
+ <button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('headerBorderStyleHelp')" [attr.aria-label]="shellText('headerBorderStyleHelp')"></button>
4292
+ @if (form.controls.headerBorderStyle.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
4293
+ </mat-form-field>
3985
4294
  <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
3986
4295
  <mat-label>{{ shellText('titleColor') }}</mat-label>
3987
4296
  <input
@@ -4285,7 +4594,7 @@ class WidgetShellEditorComponent {
4285
4594
  </div>
4286
4595
  </details>
4287
4596
  </div>
4288
- `, isInline: true, styles: [":host{display:block;container:shell-editor / inline-size}.shell-topic-panel,.shell-topic{display:grid;gap:var(--pfx-spacing-4, 16px)}.shell-topic-panel{padding-block:var(--pfx-spacing-4, 16px);min-width:0}.shell-topic-panel[hidden]{display:none}.shell-topic-panel>.shell-grid{margin-block-end:var(--pfx-spacing-4, 16px)}.shell-link-summary{display:grid;gap:var(--pfx-spacing-1, 4px);padding-block:var(--pfx-spacing-3, 12px);overflow-wrap:anywhere}.shell-button-task{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0}.shell-button-count{margin:0;font-size:14px}.shell-action-technical,.shell-action-scopes{min-width:0;font-size:12px}.shell-action-technical summary,.shell-action-scopes summary{cursor:pointer;padding-block:8px}.shell-action-technical summary:focus-visible,.shell-action-scopes summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-action-contract{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px}.shell-action-contract dd{margin:0;overflow-wrap:anywhere}.shell-action-technical .shell-action-row{margin-top:12px}.shell-editor{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;gap:20px;padding:12px 4px 20px}.shell-controls{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0;container:shell-controls / inline-size}.shell-preview-region{min-width:0}@container shell-controls (max-width: 480px){.shell-controls .shell-grid,.shell-controls .shell-action-row,.shell-controls .shell-quick-inputs,.shell-controls .shell-available-item{grid-template-columns:minmax(0,1fr)}.shell-controls .shell-available-item>button{justify-self:start}}.shell-intro{margin:0;max-width:68ch;color:var(--md-sys-color-on-surface-variant);font-size:14px;line-height:1.5}.shell-details{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:var(--pfx-spacing-4, 16px)}.shell-details summary{cursor:pointer;color:var(--md-sys-color-on-surface);font-weight:600;line-height:1.5}.shell-details summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:4px}.shell-option-content{display:grid;gap:var(--pfx-spacing-4, 16px);margin-top:var(--pfx-spacing-4, 16px);min-width:0}.shell-details>.shell-grid{margin-top:var(--pfx-spacing-4, 16px)}.shell-details>.shell-section{margin:var(--pfx-spacing-4, 16px) 0 var(--pfx-spacing-2, 8px)}.shell-validation{color:var(--md-sys-color-error);margin:0;line-height:1.5}.shell-section{margin:4px 0 -8px;color:var(--md-sys-color-on-surface);font-size:14px;font-weight:650;line-height:1.4}.shell-grid{display:grid;gap:12px 16px;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start}.shell-field--wide{grid-column:1 / -1}.shell-preset-preview__body{display:grid;align-content:start;height:100%;color:var(--md-sys-color-on-surface-variant)}.shell-preset-preview__body .shell-mini-value,.shell-preset-preview__body .shell-mini-line{background:currentColor}.shell-preset-copy strong{display:flex;align-items:baseline;justify-content:space-between;gap:var(--pfx-spacing-2, 8px)}.shell-preset-check{color:var(--md-sys-color-primary);flex-shrink:0}.shell-preset-field,.shell-preset-copy{display:grid}.shell-preset-field{gap:8px}.shell-preset-label{color:var(--md-sys-color-on-surface);font-size:13px;font-weight:600}.shell-preset-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.shell-preset-option{min-width:0;display:grid;grid-template-columns:72px minmax(0,1fr);align-items:center;gap:10px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);text-align:left;cursor:pointer}.shell-preset-option:hover{background:var(--md-sys-color-surface-container-low)}.shell-preset-option:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preset-option--selected{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 1px var(--md-sys-color-primary)}.shell-preset-preview{box-sizing:border-box;display:block;height:64px;overflow:hidden;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.shell-preset-preview__header{display:flex;align-items:center;gap:4px;padding-inline:8px;height:22px;border-bottom:1px solid transparent}.shell-preset-preview--none{border-style:dashed;border-radius:8px;background:transparent;box-shadow:none}.shell-preset-copy{gap:3px}.shell-preset-copy small{color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35}.shell-flags{display:flex;gap:16px;align-items:center}.shell-actions-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.shell-available{display:grid;gap:12px}.shell-available-toolbar{display:grid;gap:12px;grid-template-columns:minmax(200px,1fr)}.shell-available-filters{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.shell-available-list{display:grid;gap:10px}.shell-available-item{display:grid;gap:8px;grid-template-columns:minmax(0,1fr) auto;border-bottom:1px solid var(--md-sys-color-outline-variant);padding-block:12px;align-items:start;min-width:0}.shell-available-main{min-width:0;overflow-wrap:anywhere}.shell-available-title{display:flex;align-items:center;gap:10px}.shell-available-text{display:grid;gap:4px}.shell-available-label{font-weight:600}.shell-available-meta{display:flex;gap:8px;flex-wrap:wrap;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-available-desc{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-available-payload{font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-action-availability{font-size:12px;line-height:1.5;padding:8px 10px;border-inline-start:2px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.shell-available-warning{font-size:11px;color:var(--md-sys-color-error)}.shell-available-actions{display:flex;align-items:flex-start;justify-content:flex-end}.shell-badge{padding:2px 6px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);font-size:10px}.shell-code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.shell-actions{display:grid;gap:12px}.shell-action{border-top:1px solid var(--md-sys-color-outline-variant);padding-block:16px;display:grid;gap:8px}.shell-action-row{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.shell-action-actions{display:flex;justify-content:flex-end}.shell-action-hint,.shell-empty{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-hint{margin:0;line-height:1.5;align-self:center;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-quick-inputs{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));align-items:end}.shell-input-path{font-size:12px;color:var(--md-sys-color-on-surface-variant);align-self:center}.shell-editor .mat-mdc-form-field{width:100%;min-width:0}:host{display:block;min-width:0}.shell-editor>*,.shell-action>*{min-width:0}.shell-available-text,.shell-input-path{min-width:0;overflow-wrap:anywhere}.shell-preview-region{min-width:0;background:var(--md-sys-color-surface)}.shell-preview-content{display:grid;gap:12px;padding-top:12px}.shell-preview-heading{cursor:pointer;padding-block:8px}.shell-preview-heading:before{content:\"\\25b8\"}.shell-preview-region[open]>.shell-preview-heading:before{content:\"\\25be\"}.shell-preview-heading:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preview-heading,.shell-override-summary{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.shell-preview-heading strong{margin-right:auto;font-size:14px}.shell-preview-heading>span,.shell-override-summary{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-preview{padding:16px;background:var(--md-sys-color-surface-container-low);min-width:0}.shell-sample-content{display:grid;gap:8px}.shell-sample-content>span{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-sample-content strong{font-size:32px;line-height:1.15;font-variant-numeric:tabular-nums}.shell-mini-title,.shell-mini-action,.shell-mini-value,.shell-mini-line{display:block;background:var(--md-sys-color-on-surface-variant);opacity:.45;border-radius:2px}.shell-mini-title{width:24px;height:3px}.shell-mini-action{width:4px;height:4px;margin-left:auto}.shell-mini-value{width:20px;height:8px;margin:8px 8px 4px;opacity:.7}.shell-mini-line{width:32px;height:3px;margin-inline:8px}@media(max-width:640px){.shell-editor{padding-inline:0}.shell-grid,.shell-action-row,.shell-quick-inputs,.shell-available-toolbar{grid-template-columns:minmax(0,1fr)}.shell-actions-head,.shell-flags{flex-wrap:wrap}.shell-field--wide{grid-column:auto}}\n"], dependencies: [{ kind: "component", type: WidgetShellComponent, selector: "praxis-widget-shell", inputs: ["shell", "context", "dragSurfaceEnabled", "dragSurfaceLabel"], outputs: ["action", "dragSurfacePointerDown", "dragSurfaceKeydown"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: SettingsSectionTabsComponent, selector: "praxis-settings-section-tabs", inputs: ["sections", "selectedId", "label", "invalidLabel", "panelId"], outputs: ["selectedIdChange"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4597
+ `, isInline: true, styles: [":host{display:block;container:shell-editor / inline-size}.shell-effective-style{display:grid;gap:4px;margin:0;font-size:12px}.shell-effective-style span{color:var(--md-sys-color-on-surface-variant)}.shell-topic-panel,.shell-topic{display:grid;gap:var(--pfx-spacing-4, 16px)}.shell-topic-panel{padding-block:var(--pfx-spacing-4, 16px);min-width:0}.shell-topic-panel[hidden]{display:none}.shell-topic-panel>.shell-grid{margin-block-end:var(--pfx-spacing-4, 16px)}.shell-link-summary{display:grid;gap:var(--pfx-spacing-1, 4px);padding-block:var(--pfx-spacing-3, 12px);overflow-wrap:anywhere}.shell-button-task{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0}.shell-button-count{margin:0;font-size:14px}.shell-action-technical,.shell-action-scopes{min-width:0;font-size:12px}.shell-action-technical summary,.shell-action-scopes summary{cursor:pointer;padding-block:8px}.shell-action-technical summary:focus-visible,.shell-action-scopes summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-action-contract{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px}.shell-action-contract dd{margin:0;overflow-wrap:anywhere}.shell-action-technical .shell-action-row{margin-top:12px}.shell-editor{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;gap:20px;padding:12px 4px 20px}.shell-controls{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0;container:shell-controls / inline-size}.shell-preview-region{min-width:0}@container shell-controls (max-width: 480px){.shell-controls .shell-grid,.shell-controls .shell-action-row,.shell-controls .shell-quick-inputs,.shell-controls .shell-available-item{grid-template-columns:minmax(0,1fr)}.shell-controls .shell-available-item>button{justify-self:start}}.shell-intro{margin:0;max-width:68ch;color:var(--md-sys-color-on-surface-variant);font-size:14px;line-height:1.5}.shell-details{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:var(--pfx-spacing-4, 16px)}.shell-details summary{cursor:pointer;color:var(--md-sys-color-on-surface);font-weight:600;line-height:1.5}.shell-details summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:4px}.shell-option-content{display:grid;gap:var(--pfx-spacing-4, 16px);margin-top:var(--pfx-spacing-4, 16px);min-width:0}.shell-details>.shell-grid{margin-top:var(--pfx-spacing-4, 16px)}.shell-details>.shell-section{margin:var(--pfx-spacing-4, 16px) 0 var(--pfx-spacing-2, 8px)}.shell-validation{color:var(--md-sys-color-error);margin:0;line-height:1.5}.shell-section{margin:4px 0 -8px;color:var(--md-sys-color-on-surface);font-size:14px;font-weight:650;line-height:1.4}.shell-grid{display:grid;gap:12px 16px;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start}.shell-field--wide{grid-column:1 / -1}.shell-preset-preview__body{display:grid;align-content:start;height:100%;color:var(--md-sys-color-on-surface-variant)}.shell-preset-preview__body .shell-mini-value,.shell-preset-preview__body .shell-mini-line{background:currentColor}.shell-preset-copy strong{display:flex;align-items:baseline;justify-content:space-between;gap:var(--pfx-spacing-2, 8px)}.shell-preset-check{color:var(--md-sys-color-primary);flex-shrink:0}.shell-preset-field,.shell-preset-copy{display:grid}.shell-preset-field{gap:8px}.shell-preset-label{color:var(--md-sys-color-on-surface);font-size:13px;font-weight:600}.shell-preset-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.shell-preset-option{min-width:0;display:grid;grid-template-columns:72px minmax(0,1fr);align-items:center;gap:10px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);text-align:left;cursor:pointer}.shell-preset-option:hover{background:var(--md-sys-color-surface-container-low)}.shell-preset-option:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preset-option--selected{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 1px var(--md-sys-color-primary)}.shell-preset-preview{box-sizing:border-box;display:block;height:64px;overflow:hidden;border:1px solid var(--md-sys-color-outline-variant);background:var(--shell-mini-bg, var(--md-sys-color-surface));-webkit-backdrop-filter:var(--shell-mini-filter, none);backdrop-filter:var(--shell-mini-filter, none)}@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){.shell-preset-preview--backdrop{background:var(--shell-mini-fallback, var(--md-sys-color-surface))}}@media(prefers-reduced-transparency:reduce){.shell-preset-preview--backdrop{background:var(--shell-mini-fallback, var(--md-sys-color-surface));-webkit-backdrop-filter:none;backdrop-filter:none}}.shell-preset-preview__header{display:flex;align-items:center;gap:4px;padding-inline:8px;height:22px;border-bottom:1px solid transparent}.shell-preset-preview--none{border-style:dashed;border-radius:8px;background:transparent;box-shadow:none}.shell-preset-copy{gap:3px}.shell-preset-copy small{color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35}.shell-flags{display:flex;gap:16px;align-items:center}.shell-actions-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.shell-available{display:grid;gap:12px}.shell-available-toolbar{display:grid;gap:12px;grid-template-columns:minmax(200px,1fr)}.shell-available-filters{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.shell-available-list{display:grid;gap:10px}.shell-available-item{display:grid;gap:8px;grid-template-columns:minmax(0,1fr) auto;border-bottom:1px solid var(--md-sys-color-outline-variant);padding-block:12px;align-items:start;min-width:0}.shell-available-main{min-width:0;overflow-wrap:anywhere}.shell-available-title{display:flex;align-items:center;gap:10px}.shell-available-text{display:grid;gap:4px}.shell-available-label{font-weight:600}.shell-available-meta{display:flex;gap:8px;flex-wrap:wrap;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-available-desc{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-available-payload{font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-action-availability{font-size:12px;line-height:1.5;padding:8px 10px;border-inline-start:2px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.shell-available-warning{font-size:11px;color:var(--md-sys-color-error)}.shell-available-actions{display:flex;align-items:flex-start;justify-content:flex-end}.shell-badge{padding:2px 6px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);font-size:10px}.shell-code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.shell-actions{display:grid;gap:12px}.shell-action{border-top:1px solid var(--md-sys-color-outline-variant);padding-block:16px;display:grid;gap:8px}.shell-action-row{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.shell-action-actions{display:flex;justify-content:flex-end}.shell-action-hint,.shell-empty{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-hint{margin:0;line-height:1.5;align-self:center;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-quick-inputs{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));align-items:end}.shell-input-path{font-size:12px;color:var(--md-sys-color-on-surface-variant);align-self:center}.shell-editor .mat-mdc-form-field{width:100%;min-width:0}:host{display:block;min-width:0}.shell-editor>*,.shell-action>*{min-width:0}.shell-available-text,.shell-input-path{min-width:0;overflow-wrap:anywhere}.shell-preview-region{min-width:0;background:var(--md-sys-color-surface)}.shell-preview-content{display:grid;gap:12px;padding-top:12px}.shell-preview-heading{cursor:pointer;padding-block:8px}.shell-preview-heading:before{content:\"\\25b8\"}.shell-preview-region[open]>.shell-preview-heading:before{content:\"\\25be\"}.shell-preview-heading:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preview-heading,.shell-override-summary{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.shell-preview-heading strong{margin-right:auto;font-size:14px}.shell-preview-heading>span,.shell-override-summary{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-preview{padding:16px;background:var(--md-sys-color-surface-container-low);min-width:0}.shell-sample-content{display:grid;gap:8px}.shell-sample-content>span{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-sample-content strong{font-size:32px;line-height:1.15;font-variant-numeric:tabular-nums}.shell-mini-title,.shell-mini-action,.shell-mini-value,.shell-mini-line{display:block;background:var(--md-sys-color-on-surface-variant);opacity:.45;border-radius:2px}.shell-mini-title{width:24px;height:3px}.shell-mini-action{width:4px;height:4px;margin-left:auto}.shell-mini-value{width:20px;height:8px;margin:8px 8px 4px;opacity:.7}.shell-mini-line{width:32px;height:3px;margin-inline:8px}@media(max-width:640px){.shell-editor{padding-inline:0}.shell-grid,.shell-action-row,.shell-quick-inputs,.shell-available-toolbar{grid-template-columns:minmax(0,1fr)}.shell-actions-head,.shell-flags{flex-wrap:wrap}.shell-field--wide{grid-column:auto}}\n"], dependencies: [{ kind: "component", type: WidgetShellComponent, selector: "praxis-widget-shell", inputs: ["shell", "context", "dragSurfaceEnabled", "dragSurfaceLabel"], outputs: ["action", "dragSurfacePointerDown", "dragSurfaceKeydown"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i9.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i9.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: SettingsSectionTabsComponent, selector: "praxis-settings-section-tabs", inputs: ["sections", "selectedId", "label", "invalidLabel", "panelId"], outputs: ["selectedIdChange"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4289
4598
  }
4290
4599
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: WidgetShellEditorComponent, decorators: [{
4291
4600
  type: Component,
@@ -4296,6 +4605,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4296
4605
  ReactiveFormsModule,
4297
4606
  MatButtonModule,
4298
4607
  MatCheckboxModule,
4608
+ MatTooltipModule,
4299
4609
  MatFormFieldModule,
4300
4610
  MatIconModule,
4301
4611
  MatInputModule,
@@ -4371,6 +4681,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4371
4681
  <div class="shell-preset-field shell-field--wide">
4372
4682
  <span class="shell-preset-label">{{ presetTitle() }}</span>
4373
4683
  <p class="shell-hint">{{ shellText('presetHint') }}</p>
4684
+ <p class="shell-effective-style" role="status" data-testid="widget-shell-effective-style">
4685
+ <strong>{{ shellText('effectiveStyle') }}: {{ effectivePresetLabel() }}</strong>
4686
+ <span>{{ shellText(form.controls.preset.value ? 'styleFromWidget' : pagePreset ? 'styleFromPage' : 'styleFromDefault') }}</span>
4687
+ @if (appearanceOverrideCount()) { <span>{{ shellText('customAdjustments') }}: {{ appearanceOverrideCount() }} · {{ shellText('adjustmentsWin') }}</span> }
4688
+ @if (form.controls.kind.value === 'none') { <span>{{ shellText('surfaceDisabled') }}</span> }
4689
+ </p>
4374
4690
  <div class="shell-preset-grid" role="group" [attr.aria-label]="presetTitle()">
4375
4691
  <button
4376
4692
  type="button"
@@ -4396,8 +4712,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4396
4712
  <span
4397
4713
  class="shell-preset-preview"
4398
4714
  aria-hidden="true"
4399
- [style.background]="p.appearance?.card?.background || null"
4715
+ [class.shell-preset-preview--backdrop]="!!p.appearance?.card?.backdropFilter && p.appearance?.card?.backdropFilter !== 'none'"
4716
+ [style.--shell-mini-bg]="p.appearance?.card?.background || null"
4717
+ [style.--shell-mini-fallback]="p.appearance?.card?.fallbackBackground || null"
4718
+ [style.--shell-mini-filter]="p.appearance?.card?.backdropFilter || null"
4400
4719
  [style.border-color]="p.appearance?.card?.borderColor || null"
4720
+ [style.border-width]="p.appearance?.card?.borderWidth || null"
4721
+ [style.border-style]="p.appearance?.card?.borderStyle || null"
4401
4722
  [style.border-radius]="p.appearance?.card?.borderRadius || null"
4402
4723
  [style.box-shadow]="p.appearance?.card?.shadow || null"
4403
4724
  >
@@ -4405,6 +4726,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4405
4726
  class="shell-preset-preview__header"
4406
4727
  [style.background]="p.appearance?.header?.background || null"
4407
4728
  [style.border-color]="p.appearance?.header?.borderColor || null"
4729
+ [style.border-bottom-width]="p.appearance?.header?.borderWidth || null"
4730
+ [style.border-bottom-style]="p.appearance?.header?.borderStyle || null"
4408
4731
  ><span class="shell-mini-title" [style.background]="p.appearance?.header?.titleColor || null"></span><span class="shell-mini-action" [style.background]="p.appearance?.header?.iconColor || null"></span></span>
4409
4732
  <span class="shell-preset-preview__body" [style.background]="p.appearance?.body?.background || null" [style.color]="p.appearance?.body?.textColor || null">
4410
4733
  <span class="shell-mini-value"></span>
@@ -4530,6 +4853,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4530
4853
  <h3 class="shell-section">{{ shellText('advancedAppearance') }}</h3>
4531
4854
  <p class="shell-hint">{{ shellText('advancedHint') }}</p>
4532
4855
  <div class="shell-grid">
4856
+ <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4857
+ <mat-label>{{ shellText('cardFilter') }}</mat-label>
4858
+ <input matInput [formControl]="form.controls.cardFilter" placeholder="blur(16px) saturate(125%)" />
4859
+ <button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardFilterHelp')" [attr.aria-label]="shellText('cardFilterHelp')"></button>
4860
+ @if (form.controls.cardFilter.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
4861
+ </mat-form-field>
4862
+ <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4863
+ <mat-label>{{ shellText('cardFallbackBg') }}</mat-label>
4864
+ <input matInput [formControl]="form.controls.cardFallbackBg" />
4865
+ <button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardFallbackHelp')" [attr.aria-label]="shellText('cardFallbackHelp')"></button>
4866
+ @if (form.controls.cardFallbackBg.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
4867
+ </mat-form-field>
4533
4868
  <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4534
4869
  <mat-label>{{ shellText('cardBg') }}</mat-label>
4535
4870
  <input
@@ -4552,6 +4887,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4552
4887
  <mat-error>{{ appearanceError() }}</mat-error>
4553
4888
  }
4554
4889
  </mat-form-field>
4890
+ <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4891
+ <mat-label>{{ shellText('cardBorderWidth') }}</mat-label>
4892
+ <input matInput [formControl]="form.controls.cardBorderWidth" placeholder="2px" />
4893
+ <button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardBorderWidthHelp')" [attr.aria-label]="shellText('cardBorderWidthHelp')"></button>
4894
+ @if (form.controls.cardBorderWidth.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
4895
+ </mat-form-field>
4896
+ <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4897
+ <mat-label>{{ shellText('cardBorderStyle') }}</mat-label>
4898
+ <input matInput [formControl]="form.controls.cardBorderStyle" placeholder="solid" />
4899
+ <button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardBorderStyleHelp')" [attr.aria-label]="shellText('cardBorderStyleHelp')"></button>
4900
+ @if (form.controls.cardBorderStyle.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
4901
+ </mat-form-field>
4555
4902
  <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4556
4903
  <mat-label>{{ shellText('cardRadius') }}</mat-label>
4557
4904
  <input
@@ -4596,6 +4943,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4596
4943
  <mat-error>{{ appearanceError() }}</mat-error>
4597
4944
  }
4598
4945
  </mat-form-field>
4946
+ <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4947
+ <mat-label>{{ shellText('headerBorderWidth') }}</mat-label>
4948
+ <input matInput [formControl]="form.controls.headerBorderWidth" placeholder="2px" />
4949
+ <button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('headerBorderWidthHelp')" [attr.aria-label]="shellText('headerBorderWidthHelp')"></button>
4950
+ @if (form.controls.headerBorderWidth.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
4951
+ </mat-form-field>
4952
+ <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4953
+ <mat-label>{{ shellText('headerBorderStyle') }}</mat-label>
4954
+ <input matInput [formControl]="form.controls.headerBorderStyle" placeholder="solid" />
4955
+ <button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('headerBorderStyleHelp')" [attr.aria-label]="shellText('headerBorderStyleHelp')"></button>
4956
+ @if (form.controls.headerBorderStyle.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
4957
+ </mat-form-field>
4599
4958
  <mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
4600
4959
  <mat-label>{{ shellText('titleColor') }}</mat-label>
4601
4960
  <input
@@ -4899,7 +5258,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4899
5258
  </div>
4900
5259
  </details>
4901
5260
  </div>
4902
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;container:shell-editor / inline-size}.shell-topic-panel,.shell-topic{display:grid;gap:var(--pfx-spacing-4, 16px)}.shell-topic-panel{padding-block:var(--pfx-spacing-4, 16px);min-width:0}.shell-topic-panel[hidden]{display:none}.shell-topic-panel>.shell-grid{margin-block-end:var(--pfx-spacing-4, 16px)}.shell-link-summary{display:grid;gap:var(--pfx-spacing-1, 4px);padding-block:var(--pfx-spacing-3, 12px);overflow-wrap:anywhere}.shell-button-task{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0}.shell-button-count{margin:0;font-size:14px}.shell-action-technical,.shell-action-scopes{min-width:0;font-size:12px}.shell-action-technical summary,.shell-action-scopes summary{cursor:pointer;padding-block:8px}.shell-action-technical summary:focus-visible,.shell-action-scopes summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-action-contract{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px}.shell-action-contract dd{margin:0;overflow-wrap:anywhere}.shell-action-technical .shell-action-row{margin-top:12px}.shell-editor{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;gap:20px;padding:12px 4px 20px}.shell-controls{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0;container:shell-controls / inline-size}.shell-preview-region{min-width:0}@container shell-controls (max-width: 480px){.shell-controls .shell-grid,.shell-controls .shell-action-row,.shell-controls .shell-quick-inputs,.shell-controls .shell-available-item{grid-template-columns:minmax(0,1fr)}.shell-controls .shell-available-item>button{justify-self:start}}.shell-intro{margin:0;max-width:68ch;color:var(--md-sys-color-on-surface-variant);font-size:14px;line-height:1.5}.shell-details{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:var(--pfx-spacing-4, 16px)}.shell-details summary{cursor:pointer;color:var(--md-sys-color-on-surface);font-weight:600;line-height:1.5}.shell-details summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:4px}.shell-option-content{display:grid;gap:var(--pfx-spacing-4, 16px);margin-top:var(--pfx-spacing-4, 16px);min-width:0}.shell-details>.shell-grid{margin-top:var(--pfx-spacing-4, 16px)}.shell-details>.shell-section{margin:var(--pfx-spacing-4, 16px) 0 var(--pfx-spacing-2, 8px)}.shell-validation{color:var(--md-sys-color-error);margin:0;line-height:1.5}.shell-section{margin:4px 0 -8px;color:var(--md-sys-color-on-surface);font-size:14px;font-weight:650;line-height:1.4}.shell-grid{display:grid;gap:12px 16px;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start}.shell-field--wide{grid-column:1 / -1}.shell-preset-preview__body{display:grid;align-content:start;height:100%;color:var(--md-sys-color-on-surface-variant)}.shell-preset-preview__body .shell-mini-value,.shell-preset-preview__body .shell-mini-line{background:currentColor}.shell-preset-copy strong{display:flex;align-items:baseline;justify-content:space-between;gap:var(--pfx-spacing-2, 8px)}.shell-preset-check{color:var(--md-sys-color-primary);flex-shrink:0}.shell-preset-field,.shell-preset-copy{display:grid}.shell-preset-field{gap:8px}.shell-preset-label{color:var(--md-sys-color-on-surface);font-size:13px;font-weight:600}.shell-preset-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.shell-preset-option{min-width:0;display:grid;grid-template-columns:72px minmax(0,1fr);align-items:center;gap:10px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);text-align:left;cursor:pointer}.shell-preset-option:hover{background:var(--md-sys-color-surface-container-low)}.shell-preset-option:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preset-option--selected{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 1px var(--md-sys-color-primary)}.shell-preset-preview{box-sizing:border-box;display:block;height:64px;overflow:hidden;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.shell-preset-preview__header{display:flex;align-items:center;gap:4px;padding-inline:8px;height:22px;border-bottom:1px solid transparent}.shell-preset-preview--none{border-style:dashed;border-radius:8px;background:transparent;box-shadow:none}.shell-preset-copy{gap:3px}.shell-preset-copy small{color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35}.shell-flags{display:flex;gap:16px;align-items:center}.shell-actions-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.shell-available{display:grid;gap:12px}.shell-available-toolbar{display:grid;gap:12px;grid-template-columns:minmax(200px,1fr)}.shell-available-filters{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.shell-available-list{display:grid;gap:10px}.shell-available-item{display:grid;gap:8px;grid-template-columns:minmax(0,1fr) auto;border-bottom:1px solid var(--md-sys-color-outline-variant);padding-block:12px;align-items:start;min-width:0}.shell-available-main{min-width:0;overflow-wrap:anywhere}.shell-available-title{display:flex;align-items:center;gap:10px}.shell-available-text{display:grid;gap:4px}.shell-available-label{font-weight:600}.shell-available-meta{display:flex;gap:8px;flex-wrap:wrap;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-available-desc{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-available-payload{font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-action-availability{font-size:12px;line-height:1.5;padding:8px 10px;border-inline-start:2px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.shell-available-warning{font-size:11px;color:var(--md-sys-color-error)}.shell-available-actions{display:flex;align-items:flex-start;justify-content:flex-end}.shell-badge{padding:2px 6px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);font-size:10px}.shell-code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.shell-actions{display:grid;gap:12px}.shell-action{border-top:1px solid var(--md-sys-color-outline-variant);padding-block:16px;display:grid;gap:8px}.shell-action-row{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.shell-action-actions{display:flex;justify-content:flex-end}.shell-action-hint,.shell-empty{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-hint{margin:0;line-height:1.5;align-self:center;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-quick-inputs{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));align-items:end}.shell-input-path{font-size:12px;color:var(--md-sys-color-on-surface-variant);align-self:center}.shell-editor .mat-mdc-form-field{width:100%;min-width:0}:host{display:block;min-width:0}.shell-editor>*,.shell-action>*{min-width:0}.shell-available-text,.shell-input-path{min-width:0;overflow-wrap:anywhere}.shell-preview-region{min-width:0;background:var(--md-sys-color-surface)}.shell-preview-content{display:grid;gap:12px;padding-top:12px}.shell-preview-heading{cursor:pointer;padding-block:8px}.shell-preview-heading:before{content:\"\\25b8\"}.shell-preview-region[open]>.shell-preview-heading:before{content:\"\\25be\"}.shell-preview-heading:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preview-heading,.shell-override-summary{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.shell-preview-heading strong{margin-right:auto;font-size:14px}.shell-preview-heading>span,.shell-override-summary{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-preview{padding:16px;background:var(--md-sys-color-surface-container-low);min-width:0}.shell-sample-content{display:grid;gap:8px}.shell-sample-content>span{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-sample-content strong{font-size:32px;line-height:1.15;font-variant-numeric:tabular-nums}.shell-mini-title,.shell-mini-action,.shell-mini-value,.shell-mini-line{display:block;background:var(--md-sys-color-on-surface-variant);opacity:.45;border-radius:2px}.shell-mini-title{width:24px;height:3px}.shell-mini-action{width:4px;height:4px;margin-left:auto}.shell-mini-value{width:20px;height:8px;margin:8px 8px 4px;opacity:.7}.shell-mini-line{width:32px;height:3px;margin-inline:8px}@media(max-width:640px){.shell-editor{padding-inline:0}.shell-grid,.shell-action-row,.shell-quick-inputs,.shell-available-toolbar{grid-template-columns:minmax(0,1fr)}.shell-actions-head,.shell-flags{flex-wrap:wrap}.shell-field--wide{grid-column:auto}}\n"] }]
5261
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;container:shell-editor / inline-size}.shell-effective-style{display:grid;gap:4px;margin:0;font-size:12px}.shell-effective-style span{color:var(--md-sys-color-on-surface-variant)}.shell-topic-panel,.shell-topic{display:grid;gap:var(--pfx-spacing-4, 16px)}.shell-topic-panel{padding-block:var(--pfx-spacing-4, 16px);min-width:0}.shell-topic-panel[hidden]{display:none}.shell-topic-panel>.shell-grid{margin-block-end:var(--pfx-spacing-4, 16px)}.shell-link-summary{display:grid;gap:var(--pfx-spacing-1, 4px);padding-block:var(--pfx-spacing-3, 12px);overflow-wrap:anywhere}.shell-button-task{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0}.shell-button-count{margin:0;font-size:14px}.shell-action-technical,.shell-action-scopes{min-width:0;font-size:12px}.shell-action-technical summary,.shell-action-scopes summary{cursor:pointer;padding-block:8px}.shell-action-technical summary:focus-visible,.shell-action-scopes summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-action-contract{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px}.shell-action-contract dd{margin:0;overflow-wrap:anywhere}.shell-action-technical .shell-action-row{margin-top:12px}.shell-editor{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;gap:20px;padding:12px 4px 20px}.shell-controls{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0;container:shell-controls / inline-size}.shell-preview-region{min-width:0}@container shell-controls (max-width: 480px){.shell-controls .shell-grid,.shell-controls .shell-action-row,.shell-controls .shell-quick-inputs,.shell-controls .shell-available-item{grid-template-columns:minmax(0,1fr)}.shell-controls .shell-available-item>button{justify-self:start}}.shell-intro{margin:0;max-width:68ch;color:var(--md-sys-color-on-surface-variant);font-size:14px;line-height:1.5}.shell-details{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:var(--pfx-spacing-4, 16px)}.shell-details summary{cursor:pointer;color:var(--md-sys-color-on-surface);font-weight:600;line-height:1.5}.shell-details summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:4px}.shell-option-content{display:grid;gap:var(--pfx-spacing-4, 16px);margin-top:var(--pfx-spacing-4, 16px);min-width:0}.shell-details>.shell-grid{margin-top:var(--pfx-spacing-4, 16px)}.shell-details>.shell-section{margin:var(--pfx-spacing-4, 16px) 0 var(--pfx-spacing-2, 8px)}.shell-validation{color:var(--md-sys-color-error);margin:0;line-height:1.5}.shell-section{margin:4px 0 -8px;color:var(--md-sys-color-on-surface);font-size:14px;font-weight:650;line-height:1.4}.shell-grid{display:grid;gap:12px 16px;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start}.shell-field--wide{grid-column:1 / -1}.shell-preset-preview__body{display:grid;align-content:start;height:100%;color:var(--md-sys-color-on-surface-variant)}.shell-preset-preview__body .shell-mini-value,.shell-preset-preview__body .shell-mini-line{background:currentColor}.shell-preset-copy strong{display:flex;align-items:baseline;justify-content:space-between;gap:var(--pfx-spacing-2, 8px)}.shell-preset-check{color:var(--md-sys-color-primary);flex-shrink:0}.shell-preset-field,.shell-preset-copy{display:grid}.shell-preset-field{gap:8px}.shell-preset-label{color:var(--md-sys-color-on-surface);font-size:13px;font-weight:600}.shell-preset-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.shell-preset-option{min-width:0;display:grid;grid-template-columns:72px minmax(0,1fr);align-items:center;gap:10px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);text-align:left;cursor:pointer}.shell-preset-option:hover{background:var(--md-sys-color-surface-container-low)}.shell-preset-option:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preset-option--selected{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 1px var(--md-sys-color-primary)}.shell-preset-preview{box-sizing:border-box;display:block;height:64px;overflow:hidden;border:1px solid var(--md-sys-color-outline-variant);background:var(--shell-mini-bg, var(--md-sys-color-surface));-webkit-backdrop-filter:var(--shell-mini-filter, none);backdrop-filter:var(--shell-mini-filter, none)}@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){.shell-preset-preview--backdrop{background:var(--shell-mini-fallback, var(--md-sys-color-surface))}}@media(prefers-reduced-transparency:reduce){.shell-preset-preview--backdrop{background:var(--shell-mini-fallback, var(--md-sys-color-surface));-webkit-backdrop-filter:none;backdrop-filter:none}}.shell-preset-preview__header{display:flex;align-items:center;gap:4px;padding-inline:8px;height:22px;border-bottom:1px solid transparent}.shell-preset-preview--none{border-style:dashed;border-radius:8px;background:transparent;box-shadow:none}.shell-preset-copy{gap:3px}.shell-preset-copy small{color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35}.shell-flags{display:flex;gap:16px;align-items:center}.shell-actions-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.shell-available{display:grid;gap:12px}.shell-available-toolbar{display:grid;gap:12px;grid-template-columns:minmax(200px,1fr)}.shell-available-filters{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.shell-available-list{display:grid;gap:10px}.shell-available-item{display:grid;gap:8px;grid-template-columns:minmax(0,1fr) auto;border-bottom:1px solid var(--md-sys-color-outline-variant);padding-block:12px;align-items:start;min-width:0}.shell-available-main{min-width:0;overflow-wrap:anywhere}.shell-available-title{display:flex;align-items:center;gap:10px}.shell-available-text{display:grid;gap:4px}.shell-available-label{font-weight:600}.shell-available-meta{display:flex;gap:8px;flex-wrap:wrap;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-available-desc{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-available-payload{font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-action-availability{font-size:12px;line-height:1.5;padding:8px 10px;border-inline-start:2px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.shell-available-warning{font-size:11px;color:var(--md-sys-color-error)}.shell-available-actions{display:flex;align-items:flex-start;justify-content:flex-end}.shell-badge{padding:2px 6px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);font-size:10px}.shell-code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.shell-actions{display:grid;gap:12px}.shell-action{border-top:1px solid var(--md-sys-color-outline-variant);padding-block:16px;display:grid;gap:8px}.shell-action-row{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.shell-action-actions{display:flex;justify-content:flex-end}.shell-action-hint,.shell-empty{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-hint{margin:0;line-height:1.5;align-self:center;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-quick-inputs{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));align-items:end}.shell-input-path{font-size:12px;color:var(--md-sys-color-on-surface-variant);align-self:center}.shell-editor .mat-mdc-form-field{width:100%;min-width:0}:host{display:block;min-width:0}.shell-editor>*,.shell-action>*{min-width:0}.shell-available-text,.shell-input-path{min-width:0;overflow-wrap:anywhere}.shell-preview-region{min-width:0;background:var(--md-sys-color-surface)}.shell-preview-content{display:grid;gap:12px;padding-top:12px}.shell-preview-heading{cursor:pointer;padding-block:8px}.shell-preview-heading:before{content:\"\\25b8\"}.shell-preview-region[open]>.shell-preview-heading:before{content:\"\\25be\"}.shell-preview-heading:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preview-heading,.shell-override-summary{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.shell-preview-heading strong{margin-right:auto;font-size:14px}.shell-preview-heading>span,.shell-override-summary{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-preview{padding:16px;background:var(--md-sys-color-surface-container-low);min-width:0}.shell-sample-content{display:grid;gap:8px}.shell-sample-content>span{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-sample-content strong{font-size:32px;line-height:1.15;font-variant-numeric:tabular-nums}.shell-mini-title,.shell-mini-action,.shell-mini-value,.shell-mini-line{display:block;background:var(--md-sys-color-on-surface-variant);opacity:.45;border-radius:2px}.shell-mini-title{width:24px;height:3px}.shell-mini-action{width:4px;height:4px;margin-left:auto}.shell-mini-value{width:20px;height:8px;margin:8px 8px 4px;opacity:.7}.shell-mini-line{width:32px;height:3px;margin-inline:8px}@media(max-width:640px){.shell-editor{padding-inline:0}.shell-grid,.shell-action-row,.shell-quick-inputs,.shell-available-toolbar{grid-template-columns:minmax(0,1fr)}.shell-actions-head,.shell-flags{flex-wrap:wrap}.shell-field--wide{grid-column:auto}}\n"] }]
4903
5262
  }], ctorParameters: () => [{ type: undefined, decorators: [{
4904
5263
  type: Optional
4905
5264
  }, {
@@ -12858,6 +13217,129 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
12858
13217
  `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{position:fixed;inset:0;z-index:1000;pointer-events:none}.connection-editor{--pce-fg: var(--md-sys-color-on-surface, #dce6f4);--pce-strong: var(--md-sys-color-on-surface, #f8fafc);--pce-muted: var(--md-sys-color-on-surface-variant, #94a3b8);--pce-primary: var(--md-sys-color-primary, #19c8e8);--pce-secondary: var(--md-sys-color-secondary, #b46cff);--pce-tertiary: var(--md-sys-color-tertiary, #ff9f2f);--pce-success: var(--md-sys-color-tertiary, #10b981);--pce-warning: var(--md-sys-color-error, #ff9f2f);--pce-error: var(--md-sys-color-error, #ef4444);--pce-surface-base: color-mix(in srgb, var(--md-sys-color-surface, #07111f) 80%, #020617);--pce-surface: color-mix(in srgb, var(--md-sys-color-surface-container, #08101d) 78%, transparent);--pce-surface-strong: color-mix(in srgb, var(--md-sys-color-surface-container-high, #0f172a) 88%, transparent);--pce-surface-soft: color-mix(in srgb, var(--md-sys-color-surface-container-low, #080d18) 64%, transparent);--pce-outline: color-mix(in srgb, var(--md-sys-color-outline-variant, #899ab7) 46%, transparent);--pce-outline-strong: color-mix(in srgb, var(--md-sys-color-outline, #94a3b8) 58%, transparent);--pce-grid: color-mix(in srgb, var(--md-sys-color-outline-variant, #7d8fa9) 24%, transparent);--pce-glass-highlight: color-mix(in srgb, var(--md-sys-color-on-surface, #ffffff) 8%, transparent);--pce-shadow: color-mix(in srgb, #000 56%, transparent);--pce-shadow-soft: color-mix(in srgb, #000 30%, transparent);--pce-stage-bg: color-mix(in srgb, var(--md-sys-color-surface-container-lowest, #040a13) 42%, transparent);--pce-dock-bg: color-mix(in srgb, var(--md-sys-color-surface-container-high, #050912) 94%, transparent);position:absolute;inset:12px;height:calc(100vh - 24px);box-sizing:border-box;overflow:hidden;padding:16px;border-radius:18px;color:var(--pce-fg);background:radial-gradient(circle at 72% 20%,color-mix(in srgb,var(--pce-secondary) 16%,transparent),transparent 28%),radial-gradient(circle at 26% 74%,color-mix(in srgb,var(--pce-primary) 10%,transparent),transparent 34%),radial-gradient(circle at 48% 52%,color-mix(in srgb,var(--pce-tertiary) 5%,transparent),transparent 35%),linear-gradient(var(--pce-grid) 1px,transparent 1px),linear-gradient(90deg,var(--pce-grid) 1px,transparent 1px),var(--pce-surface-base);background-size:auto,auto,auto,38px 38px,38px 38px,auto;border:1px solid var(--pce-outline);box-shadow:0 28px 90px var(--pce-shadow);pointer-events:auto;display:grid;grid-template-columns:minmax(0,1fr) minmax(300px,380px);grid-template-rows:auto minmax(0,1fr) minmax(44px,118px);gap:14px}:host-context(.theme-light) .connection-editor{--pce-surface-base: color-mix(in srgb, var(--md-sys-color-surface, #ffffff) 94%, var(--md-sys-color-primary, #2563eb) 6%);--pce-surface: color-mix(in srgb, var(--md-sys-color-surface-container, #f8fafc) 82%, transparent);--pce-surface-strong: color-mix(in srgb, var(--md-sys-color-surface-container-high, #eef2ff) 88%, transparent);--pce-surface-soft: color-mix(in srgb, var(--md-sys-color-surface-container-low, #f8fafc) 70%, transparent);--pce-outline: color-mix(in srgb, var(--md-sys-color-outline-variant, #cbd5e1) 68%, transparent);--pce-outline-strong: color-mix(in srgb, var(--md-sys-color-outline, #64748b) 62%, transparent);--pce-grid: color-mix(in srgb, var(--md-sys-color-outline-variant, #cbd5e1) 34%, transparent);--pce-glass-highlight: color-mix(in srgb, #fff 64%, transparent);--pce-shadow: color-mix(in srgb, var(--md-sys-color-shadow, #64748b) 22%, transparent);--pce-shadow-soft: color-mix(in srgb, var(--md-sys-color-shadow, #64748b) 14%, transparent);--pce-stage-bg: color-mix(in srgb, var(--md-sys-color-surface-container-lowest, #ffffff) 58%, transparent);--pce-dock-bg: color-mix(in srgb, var(--md-sys-color-surface-container, #f8fafc) 92%, transparent)}.connection-editor--dock-collapsed{grid-template-rows:auto minmax(0,1fr) 48px}.connection-editor__header{grid-column:1 / -1;display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:42px;padding:8px 14px;border-radius:999px;background:var(--pce-surface);border:1px solid var(--pce-outline);box-shadow:inset 0 1px var(--pce-glass-highlight);-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.connection-editor__mode-frame{position:relative;min-width:0;flex:1 1 auto;display:flex;justify-content:center}.connection-editor__close{width:32px;height:32px;flex:0 0 auto;display:inline-grid;place-items:center;border:1px solid var(--pce-outline);border-radius:999px;color:var(--pce-text);background:var(--pce-surface-strong);box-shadow:inset 0 1px var(--pce-glass-highlight);cursor:pointer;font:inherit;font-size:1.2rem;line-height:1}.connection-editor__close:hover,.connection-editor__close:focus-visible{border-color:var(--pce-outline-strong);background:color-mix(in srgb,var(--pce-accent) 16%,var(--pce-surface-strong));outline:none}.connection-editor__mode-shell{display:inline-flex;align-items:center;gap:4px;min-width:0;padding:4px;border-radius:999px;border:1px solid var(--pce-outline);background:linear-gradient(180deg,var(--pce-surface-strong),var(--pce-surface));box-shadow:inset 0 1px var(--pce-glass-highlight),0 10px 26px var(--pce-shadow-soft);scrollbar-width:none}.connection-editor__mode-shell::-webkit-scrollbar{display:none}.connection-editor__mode{min-height:30px;display:inline-flex;align-items:center;gap:7px;padding:0 12px;border:1px solid transparent;border-radius:999px;background:transparent;color:var(--pce-muted);cursor:pointer;font-size:.76rem;font-weight:800;white-space:nowrap;transition:background .16s ease,border-color .16s ease,color .16s ease,box-shadow .16s ease}.connection-editor__mode:hover,.connection-editor__mode--active{color:var(--pce-strong);border-color:color-mix(in srgb,var(--pce-primary) 28%,transparent);background:color-mix(in srgb,var(--pce-surface-strong) 82%,transparent);box-shadow:0 0 18px color-mix(in srgb,var(--pce-primary) 12%,transparent)}.connection-editor__mode-glyph{color:var(--pce-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-size:.72rem;font-weight:900}.connection-editor__mode--active .connection-editor__mode-glyph{color:var(--pce-primary)}.connection-editor__mode-dot{width:6px;height:6px;border-radius:999px;background:var(--pce-success);box-shadow:0 0 12px color-mix(in srgb,var(--pce-success) 70%,transparent)}.connection-editor__status-pill{display:inline-flex;align-items:center;gap:8px;min-height:24px;padding:0 12px;border-radius:999px;border:1px solid color-mix(in srgb,var(--pce-primary) 36%,transparent);background:var(--pce-surface-soft);color:var(--pce-muted);font-size:.72rem;font-weight:800;white-space:nowrap}.connection-editor__status-pill--button{cursor:pointer;transition:border-color .16s ease,background .16s ease,color .16s ease,box-shadow .16s ease}.connection-editor__status-pill--button:hover,.connection-editor__status-pill--button:focus-visible{border-color:color-mix(in srgb,var(--pce-primary) 62%,transparent);background:color-mix(in srgb,var(--pce-primary) 12%,var(--pce-surface-soft));color:var(--pce-strong);box-shadow:0 0 18px color-mix(in srgb,var(--pce-primary) 14%,transparent);outline:none}.connection-editor__status-pill span{width:6px;height:6px;border-radius:999px;background:var(--pce-primary);box-shadow:0 0 12px color-mix(in srgb,var(--pce-primary) 70%,transparent)}.connection-editor__section{display:grid;gap:10px}.connection-editor__dock{grid-column:1 / -1;grid-row:3;position:relative;z-index:4;min-height:0;display:grid;border-radius:14px;border:1px solid var(--pce-outline);background:radial-gradient(circle at 16% 0%,color-mix(in srgb,var(--pce-primary) 22%,transparent),transparent 34%),radial-gradient(circle at 78% 0%,color-mix(in srgb,var(--pce-secondary) 25%,transparent),transparent 38%),linear-gradient(90deg,var(--pce-dock-bg),var(--pce-surface-strong) 46%,color-mix(in srgb,var(--pce-secondary) 14%,var(--pce-dock-bg))),var(--pce-dock-bg);box-shadow:0 18px 58px var(--pce-shadow-soft),inset 0 1px var(--pce-glass-highlight);overflow:hidden;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.connection-editor__dock--collapsed .connection-editor__dock-tabs{border-block-end:0}.connection-editor__dock-tabs{display:flex;align-items:center;gap:10px;min-height:52px;padding:9px 14px;border-block-end:1px solid var(--pce-outline);background:linear-gradient(180deg,var(--pce-glass-highlight),transparent),color-mix(in srgb,var(--pce-surface-soft) 34%,transparent)}.connection-editor__dock-tablist{display:flex;align-items:center;gap:10px;min-width:0}.connection-editor__dock-tab,.connection-editor__dock-toggle{min-height:30px;border-radius:999px;border:1px solid transparent;background:transparent;color:var(--pce-muted);cursor:pointer;font-size:.72rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase;transition:border-color .15s ease,background .15s ease,color .15s ease,box-shadow .15s ease,transform .15s ease}.connection-editor__dock-tab{display:inline-flex;align-items:center;gap:8px;padding:0 13px;white-space:nowrap}.connection-editor__dock-tab-glyph{color:var(--pce-muted);font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;font-size:.8rem;letter-spacing:0;text-transform:none}.connection-editor__dock-tab--active{color:var(--pce-strong);border-color:var(--pce-outline-strong);background:linear-gradient(180deg,color-mix(in srgb,var(--pce-strong) 14%,transparent),color-mix(in srgb,var(--pce-strong) 6%,transparent));box-shadow:0 0 0 1px color-mix(in srgb,var(--pce-shadow) 72%,transparent),0 0 22px color-mix(in srgb,var(--pce-strong) 8%,transparent),inset 0 1px var(--pce-glass-highlight)}.connection-editor__dock-tab--active .connection-editor__dock-tab-glyph{color:var(--pce-strong)}.connection-editor__dock-count{margin-inline-start:auto;color:color-mix(in srgb,var(--pce-muted) 62%,transparent);font-size:.7rem;font-weight:800;letter-spacing:.16em;text-transform:uppercase;white-space:nowrap}.connection-editor__dock-toggle{width:32px;padding:0;border-color:var(--pce-outline);background:var(--pce-surface-soft);color:var(--pce-muted);font-size:.88rem;letter-spacing:0}.connection-editor__dock-body{max-height:154px;overflow:auto;padding:12px 12px 14px;border-block-start:1px solid var(--pce-outline);background:linear-gradient(90deg,color-mix(in srgb,var(--pce-primary) 9%,transparent),color-mix(in srgb,var(--pce-secondary) 10%,transparent) 62%,color-mix(in srgb,var(--pce-surface-strong) 18%,transparent)),color-mix(in srgb,var(--pce-surface-soft) 30%,transparent)}.connection-editor__dock-body pre{max-height:60px}.connection-editor__dock-links{display:grid;gap:8px}.connection-editor__dock-link-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:10px;width:100%;padding:7px 10px;border-radius:10px;border:1px solid var(--pce-outline);background:linear-gradient(90deg,var(--pce-surface-soft),color-mix(in srgb,var(--pce-secondary) 10%,var(--pce-surface-soft)) 72%,var(--pce-surface-soft)),var(--pce-surface-soft);color:var(--pce-fg);transition:border-color .15s ease,background .15s ease,box-shadow .15s ease,transform .15s ease}.connection-editor__dock-link{display:grid;grid-template-columns:minmax(116px,.52fr) minmax(300px,1.72fr) minmax(160px,.9fr) auto;align-items:center;gap:12px;width:100%;min-height:42px;padding:0;border:0;border-radius:8px;background:transparent;color:inherit;cursor:pointer;text-align:start}.connection-editor__dock-link-row:hover,.connection-editor__dock-link--active{transform:translateY(-1px);border-color:color-mix(in srgb,var(--pce-primary) 42%,transparent);background:linear-gradient(90deg,color-mix(in srgb,var(--pce-primary) 13%,transparent),color-mix(in srgb,var(--pce-secondary) 16%,transparent),var(--pce-surface-strong)),var(--pce-surface-strong);box-shadow:0 12px 32px var(--pce-shadow-soft),inset 0 0 18px color-mix(in srgb,var(--pce-primary) 6%,transparent)}.connection-editor__dock-link--active{border-color:#facc1575;box-shadow:0 12px 32px var(--pce-shadow-soft),0 0 24px #facc151f}.connection-editor__dock-link--new{border-color:#fb923ca3;background:linear-gradient(90deg,#fb923c2e,#a855f729,#0ea5e91f),#0f172ac2;box-shadow:0 12px 34px #02061752,0 0 30px #fb923c2e;animation:new-dock-link-flash 1.35s ease-out forwards}.connection-editor__dock-link--error{border-color:#ef444461}.connection-editor__dock-jump{min-width:62px;min-height:30px;padding:0 10px;border-radius:999px;border:1px solid color-mix(in srgb,var(--pce-primary) 28%,transparent);background:color-mix(in srgb,var(--pce-surface-soft) 54%,transparent);color:var(--pce-primary);cursor:pointer;font-size:.66rem;font-weight:900;letter-spacing:.06em;text-transform:uppercase;transition:border-color .15s ease,background .15s ease,color .15s ease,box-shadow .15s ease}.connection-editor__dock-jump:hover,.connection-editor__dock-jump:focus-visible{border-color:color-mix(in srgb,var(--pce-primary) 56%,transparent);background:color-mix(in srgb,var(--pce-primary) 16%,transparent);color:var(--pce-strong);box-shadow:0 0 18px color-mix(in srgb,var(--pce-primary) 18%,transparent);outline:none}.connection-editor__dock-link-kind{display:inline-flex;align-items:center;gap:7px;min-width:0;color:var(--pce-muted);font-size:.66rem;font-weight:900;letter-spacing:.1em;text-transform:uppercase;white-space:nowrap}.connection-editor__dock-link-kind span{width:8px;height:8px;border-radius:999px;background:#b46cff;box-shadow:0 0 14px #b46cff7a}.connection-editor__dock-link--state .connection-editor__dock-link-kind span{background:#ff9f2f;box-shadow:0 0 16px #ff9f2f94}.connection-editor__dock-link--state-read .connection-editor__dock-link-kind span{background:#8b7dff;box-shadow:0 0 13px #8b7dff75}.connection-editor__dock-link--projection .connection-editor__dock-link-kind span{background:#19c8e8;box-shadow:0 0 10px #19c8e857}.connection-editor__dock-link--event-propagation .connection-editor__dock-link-kind span{background:#ffb454;box-shadow:0 0 15px #ffb45485}.connection-editor__dock-link--policy .connection-editor__dock-link-kind span{outline:1px solid rgba(34,197,94,.76);outline-offset:2px;box-shadow:0 0 16px #22c55e61}.connection-editor__dock-link--policy:not(.connection-editor__dock-link--state,.connection-editor__dock-link--state-read,.connection-editor__dock-link--projection,.connection-editor__dock-link--event-propagation) .connection-editor__dock-link-kind span{background:#22c55e}.connection-editor__dock-link-route{display:inline-flex;align-items:center;gap:8px;min-width:0}.connection-editor__dock-link-chip{min-width:0;overflow:hidden;padding:4px 8px;border-radius:999px;border:1px solid var(--pce-outline);background:color-mix(in srgb,var(--pce-surface-soft) 72%,transparent);color:var(--pce-fg);font-size:.72rem;font-weight:900;text-overflow:ellipsis;white-space:nowrap}.connection-editor__dock-link-chip--from{border-color:#ff9f2f66;color:#ffbd62}.connection-editor__dock-link-chip--to{border-color:#19c8e83d;color:#7dddeb}:host-context(.theme-light) .connection-editor__dock-link-chip--from{border-color:color-mix(in srgb,var(--pce-tertiary) 46%,transparent);color:#8a4600}:host-context(.theme-light) .connection-editor__dock-link-chip--to{border-color:color-mix(in srgb,var(--pce-primary) 34%,transparent);color:#075f73}.connection-editor__dock-link-arrow{flex:0 0 auto;color:var(--pce-muted);font-weight:900}.connection-editor__dock-link-id{min-width:0;overflow:hidden;color:var(--pce-muted);font-size:.66rem;font-weight:800;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;text-overflow:ellipsis;white-space:nowrap}.connection-editor__dock-link-badges{display:inline-flex;justify-content:flex-end;gap:5px;min-width:70px}.connection-editor__dock-link-badges span{padding:3px 7px;border-radius:999px;background:#a855f724;color:#d8b4fe;font-size:.62rem;font-weight:900;text-transform:uppercase;white-space:nowrap}:host-context(.theme-light) .connection-editor__dock-link-badges span{background:color-mix(in srgb,var(--pce-secondary) 14%,var(--pce-surface-soft));color:#6d2a9f}.connection-editor__dock-empty{padding:12px;border-radius:12px;border:1px dashed var(--pce-outline);color:var(--pce-muted);font-size:.76rem;font-weight:700}.connection-editor__inspector{border-color:color-mix(in srgb,var(--pce-primary) 26%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-primary) 10%,transparent),color-mix(in srgb,var(--pce-secondary) 8%,transparent)),var(--pce-surface)}.connection-editor__eyebrow,.connection-editor__section-title{font-size:.72rem;text-transform:uppercase;letter-spacing:.16em;color:var(--pce-muted);font-weight:700}.connection-editor__title{font-size:1.02rem;font-weight:800}.connection-editor__subtitle,.connection-editor__item-copy,.connection-editor__item-meta,.connection-editor__details-row span,.connection-editor__details-label,.connection-editor__empty{color:var(--pce-muted);font-size:.84rem}.connection-editor__inspector-heading{display:grid;gap:4px;padding:10px 12px;border-radius:14px;background:var(--pce-surface-soft);border:1px solid var(--pce-outline)}.connection-editor__inspector-heading strong{color:var(--pce-strong);font-size:.96rem;overflow-wrap:anywhere}.connection-editor__inspector-heading span{color:var(--pce-primary);font-size:.76rem;font-weight:800;text-transform:uppercase;letter-spacing:.1em}.connection-editor__human-summary{display:grid;gap:6px;padding:12px;border-radius:14px;border:1px solid color-mix(in srgb,var(--pce-primary) 24%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-primary) 12%,transparent),color-mix(in srgb,var(--pce-secondary) 8%,transparent)),var(--pce-surface-soft);color:var(--pce-fg);line-height:1.35}.connection-editor__nested-summary{display:grid;gap:6px;padding:10px;border-radius:12px;border:1px solid color-mix(in srgb,var(--pce-primary) 22%,transparent);background:color-mix(in srgb,var(--pce-primary) 8%,var(--pce-surface-soft))}.connection-editor__nested-summary strong{color:var(--pce-strong);font-size:.82rem}.connection-editor__nested-summary span:last-child{color:var(--pce-primary);font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;font-size:.72rem;overflow-wrap:anywhere}.connection-editor__recommended{border-color:color-mix(in srgb,var(--pce-secondary) 32%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-secondary) 14%,transparent),color-mix(in srgb,var(--pce-primary) 10%,transparent)),var(--pce-surface)}.connection-editor__recommended-card{display:grid;gap:8px;width:100%;padding:13px;border-radius:16px;border:1px solid color-mix(in srgb,var(--pce-secondary) 36%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-secondary) 16%,transparent),color-mix(in srgb,var(--pce-primary) 10%,transparent)),var(--pce-surface-strong);color:var(--pce-fg);text-align:start;cursor:pointer;transition:transform .16s ease,border-color .16s ease,background .16s ease}.connection-editor__recommended-card:hover{transform:translateY(-1px);border-color:color-mix(in srgb,var(--pce-primary) 52%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-secondary) 22%,transparent),color-mix(in srgb,var(--pce-primary) 14%,transparent)),var(--pce-surface-strong)}.connection-editor__recommended-card strong{color:var(--pce-strong);font-size:.92rem;line-height:1.28}.connection-editor__recommended-card span:not(.connection-editor__human-summary-label):not(.connection-editor__suggestion-action),.connection-editor__recommended-card small{color:var(--pce-muted);font-size:.76rem;line-height:1.35}.connection-editor__success{display:grid;gap:4px;padding:10px 12px;border-radius:14px;border:1px solid rgba(34,197,94,.34);background:#22c55e1f;color:var(--pce-fg);line-height:1.35}.connection-editor__success strong{color:#bbf7d0;font-size:.78rem;text-transform:uppercase;letter-spacing:.08em}.connection-editor__success span{color:var(--pce-strong);font-size:.82rem}.connection-editor__human-summary-label,.connection-editor__suggestion-intent{justify-self:start;padding:3px 8px;border-radius:999px;background:color-mix(in srgb,var(--pce-primary) 16%,transparent);color:var(--pce-fg);font-size:.66rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.connection-editor__human-summary strong{color:var(--pce-strong);font-size:.88rem}.connection-editor__human-summary span:last-child,.connection-editor__suggestion-preview{color:var(--pce-muted);font-size:.76rem;line-height:1.35}.connection-editor__technical-details{display:grid;gap:10px;padding:10px 12px;border-radius:14px;border:1px solid var(--pce-outline);background:var(--pce-surface-soft)}.connection-editor__technical-details summary{cursor:pointer;color:var(--pce-muted);font-size:.78rem;font-weight:800}.connection-editor__technical-details[open] summary{margin-bottom:10px;color:var(--pce-strong)}.connection-editor__inspector-ports{display:flex;flex-wrap:wrap;gap:6px}.connection-editor__inspector-port{max-width:100%;padding:4px 8px;border-radius:999px;border:1px solid rgba(148,163,184,.16);background:#0f172a94;color:#cbd5e1;font-size:.68rem;font-weight:800;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.connection-editor__inspector-port--input{border-color:#22d3ee57;color:#67e8f9}.connection-editor__inspector-port--output{border-color:#f59e0b57;color:#fbbf24}.connection-editor__inspector-port--state{border-color:#a855f761;color:#d8b4fe}.connection-editor__segment-kind-legend{display:grid;gap:10px;padding:12px;border-radius:16px;border:1px solid rgba(148,163,184,.14);background:linear-gradient(135deg,#0f172aa8,#080d1885),#02061757}.connection-editor__segment-kind-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.connection-editor__segment-kind-pill{min-width:0;display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:28px;padding:0 8px;border-radius:999px;border:1px solid rgba(148,163,184,.14);background:#0f172a94;color:#cbd5e1;font-size:.68rem;font-weight:900;overflow:hidden;white-space:nowrap}.connection-editor__segment-kind-pill span{width:8px;height:8px;flex:0 0 auto;border-radius:999px;background:currentColor;box-shadow:0 0 12px currentColor}.connection-editor__segment-kind-pill--input{border-color:#19c8e83d;color:#7dddeb}.connection-editor__segment-kind-pill--output{border-color:#ff9f2f61;color:#ffbd62}.connection-editor__segment-kind-pill--state{border-color:#b46cff61;color:#d9b8ff}:host-context(.theme-light) .connection-editor__segment-kind-pill{border-color:var(--pce-outline);background:color-mix(in srgb,var(--pce-surface-strong) 88%,transparent)}:host-context(.theme-light) .connection-editor__segment-kind-pill--input{color:#075f73}:host-context(.theme-light) .connection-editor__segment-kind-pill--output{color:#8a4600}:host-context(.theme-light) .connection-editor__segment-kind-pill--state{color:#6d2a9f}.connection-editor__stage{grid-column:1;grid-row:2;position:relative;min-height:0;height:100%;overflow:visible;border-radius:16px;border:1px solid var(--pce-outline);background:radial-gradient(circle at center,color-mix(in srgb,var(--pce-secondary) 7%,transparent),transparent 38%),radial-gradient(circle,var(--pce-grid) 1px,transparent 1.5px),var(--pce-stage-bg);background-size:auto,31px 31px,auto;box-shadow:inset 0 0 0 1px var(--pce-glass-highlight);cursor:grab;touch-action:none}.connection-editor__stage--panning,.connection-editor__stage:active{cursor:grabbing}.connection-editor__stage--dragging{cursor:crosshair}.connection-editor__stage--rotating{cursor:grabbing}.connection-editor__viewport{position:absolute;inset-block-start:0;inset-inline-start:0;width:900px;height:540px;overflow:visible;transform-origin:0 0;transition:transform .14s ease}.connection-editor__wires{position:absolute;inset:0;width:100%;height:100%;overflow:visible}.connection-editor__wire,.connection-editor__wire-hit{fill:none;stroke-linecap:round}.connection-editor__wire{stroke:#b46cff;stroke-width:1.45;stroke-opacity:.48;pointer-events:none;filter:url(#connection-glow);stroke-dasharray:10 8;animation:connection-flow 1.65s linear infinite}.connection-editor__wire-hit{stroke:transparent;stroke-width:24;cursor:pointer;pointer-events:stroke}.connection-editor__inspector-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.connection-editor__wire-hit--active{stroke-width:24}.connection-editor__wire--halo{stroke:#b46cff1f;stroke-width:7;stroke-dasharray:none;animation:none}.connection-editor__wire--state{stroke:#ff9f2f;stroke-width:2.25;stroke-dasharray:8 7;animation:connection-flow .92s linear infinite}.connection-editor__wire--state.connection-editor__wire--halo{stroke:#ff9f2f29;stroke-width:11}.connection-editor__wire--projection{stroke:#15b8d8;stroke-width:1.75;stroke-dasharray:20 8;animation:connection-flow 1.9s linear infinite}.connection-editor__wire--projection.connection-editor__wire--halo{stroke:#15b8d81a;stroke-width:7}.connection-editor__wire--state-read{stroke:#8b7dff;stroke-width:1.65;stroke-dasharray:3 11;animation:connection-flow 2.25s linear infinite reverse}.connection-editor__wire--state-read.connection-editor__wire--halo{stroke:#8b7dff21;stroke-width:8}.connection-editor__wire--event-propagation{stroke:#ffb454;stroke-width:2.05;stroke-dasharray:5 7;animation:connection-flow .78s linear infinite}.connection-editor__wire--event-propagation.connection-editor__wire--halo{stroke:#ffb4542e;stroke-width:10}.connection-editor__wire--policy{filter:drop-shadow(0 0 6px rgba(34,197,94,.2)) url(#connection-glow)}.connection-editor__wire--policy.connection-editor__wire--halo{stroke:#22c55e1f;stroke-width:13}.connection-editor__wire--warning{stroke:#ff9f2f}.connection-editor__wire--warning.connection-editor__wire--halo{stroke:#ff9f2f2e}.connection-editor__wire--error{stroke:#ef4444}.connection-editor__wire--error.connection-editor__wire--halo{stroke:#ef44442e}.connection-editor__wire--active{stroke:#d9b8ff;stroke-width:3;stroke-opacity:1;stroke-dasharray:5 7;animation-duration:.58s}.connection-editor__wire--halo.connection-editor__wire--active{stroke-width:16;stroke-opacity:.8;animation:selected-wire-aura 1.35s ease-in-out infinite}.connection-editor__wire--new{stroke-width:3.4;stroke-opacity:1;stroke-dasharray:640;stroke-dashoffset:640;animation:wire-draw-in .62s cubic-bezier(.16,1,.3,1) forwards,connection-flow 1.05s linear .62s infinite}.connection-editor__wire--halo.connection-editor__wire--new{stroke-width:22;stroke-opacity:0;animation:new-wire-halo 1.45s ease-out forwards}.connection-editor__wire--trace{stroke:#9b5cff;stroke-width:3.2;stroke-opacity:1;stroke-dasharray:7 6;animation:connection-flow .72s linear infinite,trace-wire-pulse 1.18s ease-in-out infinite}.connection-editor__wire--halo.connection-editor__wire--trace{stroke:#9b5cff57;stroke-width:18;stroke-opacity:.86;animation:trace-halo-pulse 1.18s ease-in-out infinite}.connection-editor__wire--mode-dimmed{stroke-opacity:.14;stroke-width:1.05;filter:none;animation-duration:2.6s}.connection-editor--guided .connection-editor__wire:not(.connection-editor__wire--active,.connection-editor__wire--new,.connection-editor__wire--trace){stroke-opacity:.85;filter:none;animation:none}.connection-editor--guided .connection-editor__wire--halo:not(.connection-editor__wire--active,.connection-editor__wire--new,.connection-editor__wire--trace){stroke-opacity:.08;animation:none}.connection-editor__wire--halo.connection-editor__wire--mode-dimmed{stroke-opacity:.05;stroke-width:5;animation:none}.connection-editor__wire-hit--mode-dimmed{stroke-width:12}.connection-editor__wire-endpoint{fill:#f8fafc;stroke:#0f172ae6;stroke-width:1.5;filter:drop-shadow(0 0 8px rgba(216,180,254,.8));pointer-events:none;animation:endpoint-pulse 1.2s ease-in-out infinite}.connection-editor__wire-endpoint--source{fill:#ff9f2f}.connection-editor__wire-endpoint--target{fill:#19c8e8}.connection-editor__connection-tooltip{position:absolute;z-index:6;width:min(312px,calc(100% - 24px));display:grid;gap:8px;padding:12px 14px;border-radius:14px;border:1px solid var(--pce-outline);background:linear-gradient(135deg,var(--pce-surface-strong),var(--pce-surface)),var(--pce-surface-strong);box-shadow:0 18px 44px var(--pce-shadow-soft),inset 0 1px var(--pce-glass-highlight);color:var(--pce-fg);pointer-events:none;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);animation:tooltip-rise .14s ease-out}.connection-editor__connection-tooltip-callout{position:absolute;inset-block-start:100%;inset-inline-start:var(--connection-tooltip-callout-x, 42px);width:2px;height:28px;border-radius:999px;background:linear-gradient(180deg,#e2e8f0bd,#19c8e800);box-shadow:0 0 12px #19c8e852;transform:translate(-1px);pointer-events:none}.connection-editor__connection-tooltip-callout:before,.connection-editor__connection-tooltip-callout:after{content:\"\";position:absolute;border-radius:999px;pointer-events:none}.connection-editor__connection-tooltip-callout:before{inset-block-start:-5px;inset-inline-start:-5px;width:12px;height:12px;border:1px solid rgba(148,163,184,.24);background:#080d18f5;box-shadow:inset 0 1px #ffffff14}.connection-editor__connection-tooltip-callout:after{inset-block-end:-3px;inset-inline-start:-4px;width:10px;height:10px;background:#19c8e8;box-shadow:0 0 16px #19c8e88f}.connection-editor__connection-tooltip--projection{border-color:#19c8e833;background:linear-gradient(135deg,#15b8d81f,#0f172ae0),#070c16eb}.connection-editor__connection-tooltip--state{border-color:#ff9f2f66;background:linear-gradient(135deg,#ff9f2f3d,#0f172ae0),#070c16eb}.connection-editor__connection-tooltip--state .connection-editor__connection-tooltip-callout{background:linear-gradient(180deg,#ffbd62d1,#ff9f2f00);box-shadow:0 0 14px #ff9f2f61}.connection-editor__connection-tooltip--state .connection-editor__connection-tooltip-callout:after{background:#ff9f2f;box-shadow:0 0 18px #ff9f2fad}.connection-editor__connection-tooltip--error{border-color:#f871716b}.connection-editor__connection-tooltip--error .connection-editor__connection-tooltip-callout:after{background:#fb7185;box-shadow:0 0 18px #fb7185ad}.connection-editor__connection-tooltip-kind{display:inline-flex;align-items:center;gap:8px;color:#94a3b8;font-size:.62rem;font-weight:900;letter-spacing:.16em;text-transform:uppercase}.connection-editor__connection-tooltip-kind span{width:8px;height:8px;border-radius:999px;background:#19c8e8;box-shadow:0 0 12px #19c8e880}.connection-editor__connection-tooltip--state .connection-editor__connection-tooltip-kind span{background:#ff9f2f;box-shadow:0 0 16px #ff9f2fc7}.connection-editor__connection-tooltip-row{min-width:0;display:grid;grid-template-columns:54px minmax(0,1fr);align-items:center;gap:8px;min-height:28px;padding:0 10px;border-radius:999px;background:#0f172a94}.connection-editor__connection-tooltip-row span{color:#8d9bb0;font-size:.62rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.connection-editor__connection-tooltip-row strong{min-width:0;overflow:hidden;color:#e2e8f0;font-size:.75rem;font-weight:900;text-overflow:ellipsis;white-space:nowrap}.connection-editor__connection-tooltip-badges{display:flex;flex-wrap:wrap;gap:6px}.connection-editor__connection-tooltip-badges span{padding:3px 8px;border-radius:999px;background:#02061799;color:#fbbf24;font-size:.66rem;font-weight:900}.connection-editor__trace-overlay{position:absolute;z-index:6;inset-block-start:16px;inset-inline-start:50%;width:min(560px,calc(100% - 360px));min-width:320px;padding:12px 14px;border-radius:16px;border:1px solid rgba(34,197,94,.26);background:linear-gradient(135deg,#22c55e1f,#0ea5e91a),#080d18f0;box-shadow:0 22px 56px #02061775,0 0 42px #22c55e1f;transform:translate(-50%);pointer-events:auto;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);animation:tooltip-rise .16s ease-out}.connection-editor__trace-overlay-head,.connection-editor__trace-overlay-route{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0}.connection-editor__trace-overlay-head span,.connection-editor__trace-overlay-route span{color:#86efac;font-size:.66rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase;white-space:nowrap}.connection-editor__trace-overlay-head strong{color:#cbd5e1;font-size:.7rem;font-weight:900;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace}.connection-editor__trace-overlay-route{margin-top:6px}.connection-editor__trace-overlay-controls{display:grid;grid-template-columns:28px minmax(0,1fr) 28px;align-items:center;gap:8px;margin-top:10px}.connection-editor__trace-overlay-button{width:28px;height:24px;display:inline-grid;place-items:center;border:1px solid rgba(148,163,184,.18);border-radius:999px;background:#0206176b;color:#e2e8f0;cursor:pointer;font-size:1rem;font-weight:900;line-height:1;transition:border-color .15s ease,background .15s ease,color .15s ease,opacity .15s ease}.connection-editor__trace-overlay-button:hover:not(:disabled){border-color:#22d3ee6b;background:#0ea5e929;color:#f8fafc}.connection-editor__trace-overlay-button:disabled,.connection-editor__trace-overlay-button[aria-disabled=true]{cursor:default;opacity:.36}.connection-editor__trace-overlay-steps{min-width:0;display:flex;align-items:center;gap:6px;overflow-x:auto;padding:2px;scrollbar-width:none}.connection-editor__trace-overlay-steps::-webkit-scrollbar{display:none}.connection-editor__trace-overlay-step{position:relative;flex:1 0 28px;min-width:28px;height:22px;border:1px solid rgba(148,163,184,.16);border-radius:999px;background:#0f172a9e;color:#94a3b8;cursor:pointer;font-size:.62rem;font-weight:900;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;transition:border-color .15s ease,background .15s ease,color .15s ease,box-shadow .15s ease}.connection-editor__trace-overlay-step--active{border-color:#22d3eea3;background:linear-gradient(90deg,#22d3ee3d,#9b5cff33);color:#f8fafc;box-shadow:0 0 18px #22d3ee33}.connection-editor__trace-overlay-step--blocked{border-color:#fb71855c;color:#fecdd3}.connection-editor__trace-overlay-route strong{min-width:0;overflow:hidden;color:#f8fafc;font-size:.82rem;font-weight:900;text-overflow:ellipsis;white-space:nowrap}.connection-editor__trace-overlay-bar{position:relative;height:5px;margin-top:10px;overflow:hidden;border-radius:999px;background:#94a3b829}.connection-editor__trace-overlay-bar span{position:absolute;inset-block:0;inset-inline-start:0;border-radius:inherit;background:linear-gradient(90deg,#22c55e,#22d3ee);box-shadow:0 0 18px #22c55e61;min-width:8%}.connection-editor__drag-wire{fill:none;stroke:#94a3b8;stroke-width:2.4;stroke-linecap:round;stroke-dasharray:6 7;pointer-events:none;filter:url(#connection-glow);animation:connection-flow 1.05s linear infinite,drag-wire-pulse 1.2s ease-in-out infinite}.connection-editor__drag-wire--halo{stroke:#94a3b829;stroke-width:14;stroke-dasharray:none;animation:drag-halo-pulse 1.2s ease-in-out infinite}.connection-editor__drag-wire--valid{stroke:#22c55e}.connection-editor__drag-wire--halo.connection-editor__drag-wire--valid{stroke:#22c55e47}.connection-editor__drag-endpoint{fill:#94a3b8;stroke:#020617eb;stroke-width:2;pointer-events:none;filter:url(#connection-glow);transform-box:fill-box;transform-origin:center;animation:endpoint-pulse 1s ease-in-out infinite}.connection-editor__drag-endpoint--source{fill:#ff9f2f}.connection-editor__drag-endpoint--target{fill:#7dddeb}.connection-editor__drag-endpoint--valid{fill:#22c55e;stroke:#bbf7d0e0}.connection-editor__wire-handle-line{stroke-width:2;stroke-linecap:round;stroke-dasharray:4 5;pointer-events:none;opacity:.88;filter:url(#connection-glow)}.connection-editor__wire-handle-line--source{stroke:#ff9f2ff0}.connection-editor__wire-handle-line--target{stroke:#19c8e8c2}.connection-editor__wire-control{fill:#0f172af5;stroke:#cbd5e1c7;stroke-width:1.8;pointer-events:none;filter:url(#connection-glow)}.connection-editor__wire-label{pointer-events:none;filter:drop-shadow(0 0 10px rgba(2,6,23,.44));animation:tooltip-rise .14s ease-out}.connection-editor__wire-label rect{fill:#080d18eb;stroke-width:1}.connection-editor__wire-label text{fill:#e2e8f0;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;font-size:8px;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.connection-editor__wire-label--source rect{stroke:#ff9f2fad}.connection-editor__wire-label--target rect{stroke:#19c8e86b}.connection-editor__zoom-controls{position:absolute;inset-inline-start:14px;inset-block-start:14px;z-index:4;display:grid;grid-template-columns:max-content;gap:6px;width:46px;pointer-events:auto}.connection-editor__tool-group,.connection-editor__trace-source{display:grid;gap:6px;padding:6px;border-radius:12px;border:1px solid var(--pce-outline);background:linear-gradient(180deg,var(--pce-surface-strong),var(--pce-surface));box-shadow:0 16px 36px var(--pce-shadow-soft),inset 0 1px var(--pce-glass-highlight);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px)}.connection-editor__tool-group{grid-template-columns:30px;width:max-content}.connection-editor__tool-group:nth-of-type(2){grid-template-columns:30px}.connection-editor__arrange-button{width:34px;min-height:34px;display:inline-flex;align-items:center;justify-content:center;gap:0;padding:0;border-radius:999px;border:1px solid rgba(168,85,247,.34);background:linear-gradient(90deg,#a855f72e,#22d3ee1f);color:#e9d5ff;font-size:0;font-weight:800;letter-spacing:0;cursor:pointer;opacity:1;transform:scale(1);transition:opacity .14s ease,transform .14s ease,border-color .14s ease,background .14s ease}.connection-editor--guided .connection-editor__arrange-button{opacity:.34;transform:scale(.9);border-color:color-mix(in srgb,var(--pce-outline) 82%,transparent);background:color-mix(in srgb,var(--pce-surface-soft) 84%,transparent)}.connection-editor--guided .connection-editor__zoom-controls:hover .connection-editor__arrange-button,.connection-editor--guided .connection-editor__zoom-controls:focus-within .connection-editor__arrange-button,.connection-editor--guided .connection-editor__arrange-button:hover,.connection-editor--guided .connection-editor__arrange-button:focus-visible,.connection-editor--guided .connection-editor__arrange-button--active{opacity:1;transform:scale(1);border-color:#a855f757;background:linear-gradient(90deg,#a855f72e,#22d3ee1f)}.connection-editor__arrange-button span{color:#c084fc;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-size:.78rem;font-weight:900}.connection-editor__tool-button{width:30px;height:30px;border-radius:999px;border:1px solid var(--pce-outline);background:var(--pce-surface-soft);color:var(--pce-fg);font-size:.9rem;font-weight:800;letter-spacing:.02em;cursor:pointer;line-height:1}.connection-editor__tool-button:hover:not(:disabled):not([aria-disabled=true]){border-color:color-mix(in srgb,var(--pce-primary) 42%,transparent);color:var(--pce-primary);box-shadow:0 0 18px color-mix(in srgb,var(--pce-primary) 16%,transparent)}.connection-editor__tool-button[aria-disabled=true]{cursor:default;opacity:.42}.connection-editor__history-button:disabled{cursor:not-allowed;opacity:.42}.connection-editor__arrange-button--active{border-color:#22d3ee94;background:linear-gradient(90deg,#0ea5e93d,#22d3ee2e);color:#cffafe}.connection-editor__trace-source{gap:4px;max-height:230px;overflow:auto}.connection-editor__trace-source-title{padding:2px 4px 6px;color:var(--pce-muted);font-size:.64rem;font-weight:900;letter-spacing:.18em;text-transform:uppercase}.connection-editor__trace-source-item{min-width:0;min-height:30px;display:flex;align-items:center;gap:8px;padding:0 8px;border:0;border-radius:8px;background:transparent;color:var(--pce-fg);cursor:pointer;font-size:.74rem;font-weight:800;text-align:start}.connection-editor__trace-source-item span{color:#f59e0b;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-size:.72rem;font-weight:900}.connection-editor__trace-source-item:hover,.connection-editor__trace-source-item--active{background:var(--pce-surface-strong);color:var(--pce-strong);box-shadow:inset 3px 0 #f59e0bd1}.connection-editor__minimap{position:absolute;inset-inline-end:14px;inset-block-end:14px;z-index:4;width:184px;height:124px;padding:18px 10px 10px;overflow:hidden;border-radius:16px;border:1px solid color-mix(in srgb,var(--pce-primary) 30%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-primary) 12%,transparent),var(--pce-surface-strong)),var(--pce-surface);box-shadow:0 20px 48px var(--pce-shadow-soft),inset 0 1px var(--pce-glass-highlight);cursor:pointer;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);opacity:1;transform:scale(1);transform-origin:100% 100%;transition:opacity .16s ease,transform .16s ease,border-color .16s ease,box-shadow .16s ease}.connection-editor--guided .connection-editor__minimap{opacity:.54;transform:scale(.88);border-color:color-mix(in srgb,var(--pce-outline) 64%,transparent);box-shadow:0 14px 34px color-mix(in srgb,var(--pce-shadow-soft) 70%,transparent),inset 0 1px var(--pce-glass-highlight)}.connection-editor--guided .connection-editor__minimap:hover,.connection-editor--guided .connection-editor__minimap:focus-visible,.connection-editor--guided .connection-editor__minimap--panning{opacity:1;transform:scale(1)}.connection-editor__minimap:hover,.connection-editor__minimap--panning{border-color:#22d3ee75;box-shadow:0 20px 48px var(--pce-shadow-soft),0 0 24px color-mix(in srgb,var(--pce-primary) 14%,transparent),inset 0 1px var(--pce-glass-highlight)}.connection-editor__minimap--panning{cursor:grabbing}.connection-editor__minimap:before{content:\"\";position:absolute;inset:22px 10px 10px;border-radius:10px;background-image:radial-gradient(circle,rgba(125,211,252,.18) 1px,transparent 1px);background-size:22px 22px;opacity:.58;pointer-events:none}.connection-editor__minimap-title{position:absolute;inset-block-start:7px;inset-inline-start:12px;color:var(--pce-muted);font-size:.6rem;font-weight:900;letter-spacing:.16em;text-transform:uppercase}.connection-editor__minimap svg{position:relative;width:100%;height:100%;display:block;overflow:visible}.connection-editor__minimap-wire{fill:none;stroke:#b46cff8f;stroke-width:4;stroke-linecap:round;opacity:.86}.connection-editor__minimap-wire--state{stroke:#ff9f2fdb}.connection-editor__minimap-wire--state-read{stroke:#8b7dffb8;stroke-dasharray:4 9}.connection-editor__minimap-wire--projection{stroke:#19c8e88a;stroke-dasharray:18 8}.connection-editor__minimap-wire--event-propagation{stroke:#ffb454cc;stroke-dasharray:6 7}.connection-editor__minimap-wire--policy{filter:drop-shadow(0 0 6px rgba(34,197,94,.28))}.connection-editor__minimap-wire--trace{stroke:#9b5cffe0;stroke-width:5;stroke-dasharray:12 9;animation:connection-flow 1s linear infinite}.connection-editor__minimap-wire--active{stroke:#facc15eb;stroke-width:6;filter:drop-shadow(0 0 8px rgba(250,204,21,.38))}.connection-editor__minimap-wire--mode-dimmed{opacity:.2;stroke-width:2.6;filter:none}.connection-editor__minimap-node{fill:#f59e0be0;stroke:#020617db;stroke-width:4;filter:drop-shadow(0 0 7px rgba(245,158,11,.24))}.connection-editor__minimap-node--state{fill:#a855f7e6;filter:drop-shadow(0 0 8px rgba(168,85,247,.34))}.connection-editor__minimap-node--selected{fill:#facc15f5;stroke:#fef08aeb;filter:drop-shadow(0 0 10px rgba(250,204,21,.42))}.connection-editor__minimap-node--trace-source{fill:#facc15f5;filter:drop-shadow(0 0 10px rgba(250,204,21,.44))}.connection-editor__minimap-node--trace-reacting{fill:#22c55eeb;filter:drop-shadow(0 0 10px rgba(34,197,94,.42))}.connection-editor__minimap-node--mode-dimmed{opacity:.28;filter:none}.connection-editor__minimap-viewport{fill:#7dd3fc1f;stroke:#7dd3fce6;stroke-width:5;stroke-dasharray:18 10;filter:drop-shadow(0 0 10px rgba(125,211,252,.3));transition:x .12s ease,y .12s ease,width .12s ease,height .12s ease}.connection-editor__minimap--panning .connection-editor__minimap-viewport{fill:#22d3ee2e;stroke:#22d3eef5;stroke-dasharray:none}.connection-editor__node{position:absolute;z-index:10;width:168px;height:168px;transform:translate(-84px,-84px);border:0;padding:0;background:transparent;color:inherit;cursor:pointer;transition:filter .16s ease}.connection-editor__node--dragging{cursor:grabbing;filter:drop-shadow(0 0 22px rgba(34,211,238,.26));z-index:12}.connection-editor__node--connection-focus{filter:drop-shadow(0 0 16px rgba(34,211,238,.2))}.connection-editor__node--compat-target .connection-editor__node-ring{box-shadow:0 0 0 2px #22c55e66,0 0 0 18px #22c55e0b,0 0 32px #22c55e29}.connection-editor__node--mode-dimmed{opacity:.42;filter:grayscale(.22) saturate(.56)}.connection-editor__node--quiet{opacity:.58;filter:saturate(.68) brightness(.86)}.connection-editor__node--mode-dimmed .connection-editor__node-ring{animation:none;box-shadow:0 0 0 1px #2dd4bf14,0 0 14px #0ea5e90f}.connection-editor__node--mode-dimmed .connection-editor__node-halo{stroke-opacity:.28}.connection-editor__node-ring,.connection-editor__node-core,.connection-editor__node-svg{position:absolute;inset:0}.connection-editor__node-ring,.connection-editor__node-core{display:grid;place-items:center;clip-path:polygon(30% 0,70% 0,100% 30%,100% 70%,70% 100%,30% 100%,0 70%,0 30%)}.connection-editor__node-ring{background:radial-gradient(circle at center,transparent 0 44%,rgba(7,17,31,.96) 45% 57%,transparent 58%),conic-gradient(from -28deg,#06b6d4b3 0 28%,#f59e0b9e 28% 48%,#a855f780 48% 68%,#06b6d4a3 68% 100%);box-shadow:0 0 0 1px #2dd4bf2e,0 0 26px #0ea5e929;animation:node-breathe 2.8s ease-in-out infinite}.connection-editor__node-svg{overflow:visible;filter:drop-shadow(0 0 16px rgba(14,165,233,.16))}.connection-editor__node-halo{fill:transparent;stroke:#38bdf838;stroke-width:1;stroke-dasharray:0;pointer-events:none}.connection-editor__node-halo:nth-child(2){stroke:#38bdf824}.connection-editor__node-halo:nth-child(3){stroke:#94a3b81a}.connection-editor__node-segment{fill-opacity:.28;stroke-opacity:.72;stroke-width:1.15;transition:fill-opacity .16s ease,stroke-opacity .16s ease,stroke-width .16s ease;cursor:grab}.connection-editor__node:hover .connection-editor__node-segment,.connection-editor__node--selected .connection-editor__node-segment,.connection-editor__node--connection-focus .connection-editor__node-segment{fill-opacity:.54;stroke-opacity:.95;stroke-width:1.55}.connection-editor__node-segment--connection-focus{fill-opacity:.76;stroke-opacity:1;stroke-width:2;filter:drop-shadow(0 0 8px rgba(34,211,238,.76));animation:segment-focus-sweep 1.45s ease-in-out infinite}.connection-editor__node-segment--source{fill-opacity:.86;stroke-opacity:1;stroke-width:2.45;filter:drop-shadow(0 0 12px rgba(251,146,60,.82))}.connection-editor__node-segment--compatible{fill-opacity:.74;stroke-opacity:1;stroke-width:2;filter:drop-shadow(0 0 10px rgba(34,197,94,.72));animation:snap-pulse 1s ease-in-out infinite}.connection-editor__node-segment--compat-preview{fill-opacity:.86;stroke-opacity:1;stroke-width:2.45;filter:drop-shadow(0 0 13px rgba(245,158,11,.78))}.connection-editor__node-segment--candidate{fill-opacity:.92;stroke-opacity:1;stroke-width:2.8;filter:drop-shadow(0 0 16px rgba(34,197,94,.86))}.connection-editor__node-segment--incompatible{fill-opacity:.12;stroke-opacity:.28;cursor:not-allowed}.connection-editor__node-segment--invalid-drop{fill-opacity:.72;stroke:#fb7185;stroke-opacity:1;stroke-width:3;filter:drop-shadow(0 0 16px rgba(244,63,94,.84));animation:invalid-drop-jolt .36s ease-out}.connection-editor__node-segment--state{fill-opacity:.44;filter:drop-shadow(0 0 8px rgba(180,108,255,.2))}.connection-editor__node-segment--mode-dimmed{fill-opacity:.08;stroke-opacity:.2;stroke-width:.9;filter:none}.connection-editor__node-anchor{opacity:.95;filter:drop-shadow(0 0 6px currentColor)}.connection-editor__node-rotate-handle{cursor:grab;opacity:0;pointer-events:none;transition:opacity .16s ease,filter .16s ease;filter:drop-shadow(0 0 8px rgba(34,211,238,.28))}.connection-editor__node:hover .connection-editor__node-rotate-handle,.connection-editor__node--selected .connection-editor__node-rotate-handle,.connection-editor__node--rotating .connection-editor__node-rotate-handle{opacity:1;pointer-events:auto}.connection-editor__node--rotating .connection-editor__node-rotate-handle{cursor:grabbing;filter:drop-shadow(0 0 12px rgba(34,211,238,.54))}.connection-editor__node-rotate-hit{fill:transparent}.connection-editor__node-rotate-dot{fill:var(--pce-surface-strong);stroke:var(--pce-primary);stroke-opacity:.88;stroke-width:1.4}.connection-editor__node-rotate-arc,.connection-editor__node-rotate-arrow{fill:none;stroke:var(--pce-primary);stroke-linecap:round;stroke-linejoin:round;stroke-opacity:.96;stroke-width:1.2;pointer-events:none}.connection-editor__segment-icon,.connection-editor__segment-label{pointer-events:none;fill:#cbd5e1;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-weight:800;paint-order:stroke;stroke:#020617c2;stroke-width:3px;stroke-linejoin:round}.connection-editor__segment-icon{font-size:12px;fill:#19c8e8;opacity:1;transition:opacity .14s ease}.connection-editor__segment-icon--output{fill:#ff9f2f}.connection-editor--guided .connection-editor__segment-icon:not(.connection-editor__segment-icon--guided-visible){opacity:0}.connection-editor--guided .connection-editor__node--rotating .connection-editor__segment-icon{opacity:0}.connection-editor__segment-label{font-size:9.5px;letter-spacing:0;fill:#e2e8f0e0;opacity:1;transition:opacity .14s ease}.connection-editor--guided .connection-editor__segment-label:not(.connection-editor__segment-label--guided-visible){opacity:0}.connection-editor--guided .connection-editor__node--rotating .connection-editor__segment-label{opacity:0}.connection-editor__node-core{inset:43px;padding:12px;border-radius:999px;clip-path:none;display:flex;flex-direction:column;align-items:center;justify-content:center;background:radial-gradient(circle at center,#111827 0 58%,#060b14);border:1px solid rgba(226,232,240,.1);box-shadow:0 12px 28px #00000073,inset 0 1px #ffffff0d;color:#f8fafc;text-shadow:0 1px 2px rgba(0,0,0,.62)}.connection-editor__node-icon{display:block;max-width:44px;overflow:hidden;color:#f8fafc;font-family:Material Symbols Outlined,Material Icons,sans-serif;font-size:1.22rem;font-weight:400;line-height:1;text-align:center;text-transform:none;letter-spacing:0;white-space:nowrap;filter:drop-shadow(0 0 10px rgba(226,232,240,.18))}.connection-editor__node--state .connection-editor__node-ring{background:radial-gradient(circle at center,transparent 0 44%,rgba(7,17,31,.96) 45% 57%,transparent 58%),conic-gradient(from 14deg,#a855f7c7 0 42%,#7e3af26b 42% 78%,#f59e0b47 78% 100%);box-shadow:0 0 0 1px #a855f738,0 0 30px #a855f733}.connection-editor__node--selected .connection-editor__node-ring{box-shadow:0 0 0 2px #facc15bd,0 0 34px #facc153d}.connection-editor__node--connection-focus .connection-editor__node-ring{box-shadow:0 0 0 2px #22d3eead,0 0 38px #22d3ee42;animation:node-focus-breathe 1.7s ease-in-out infinite}.connection-editor__node--trace-source .connection-editor__node-ring{box-shadow:0 0 0 2px #facc15c7,0 0 0 18px #facc150f,0 0 54px #facc154d;animation:trace-source-pulse 1.38s ease-in-out infinite}.connection-editor__node--trace-source:before{content:\"\";position:absolute;z-index:0;inset:-44px;border-radius:999px;border:2px solid rgba(250,204,21,.36);background:radial-gradient(circle at center,transparent 0 64%,rgba(250,204,21,.05) 65% 66%,transparent 67%),conic-gradient(from -20deg,#facc1500,#facc1552,#facc1500);box-shadow:0 0 36px #facc151f;opacity:.92;pointer-events:none;animation:trace-source-orbit 2.8s linear infinite}.connection-editor__node--trace-reacting .connection-editor__node-ring{box-shadow:0 0 0 2px #22c55e94,0 0 46px #22c55e38;animation:trace-reacting-pulse 1.38s ease-in-out infinite}.connection-editor__node--trace-source:after,.connection-editor__node--trace-reacting:after{position:absolute;z-index:3;inset-inline-start:50%;inset-block-start:-42px;padding:0;border:0;background:transparent;color:#facc15;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-size:.68rem;font-weight:900;letter-spacing:.18em;text-transform:uppercase;transform:translate(-50%);text-shadow:0 0 12px rgba(250,204,21,.52);pointer-events:none}.connection-editor__node--trace-source:after{content:attr(data-trace-label)}.connection-editor__node--trace-reacting:after{content:attr(data-trace-label);color:#86efac;text-shadow:0 0 12px rgba(34,197,94,.44)}.connection-editor__node--trace-source .connection-editor__node-segment--output{fill-opacity:.66;stroke-width:2.2;filter:drop-shadow(0 0 14px rgba(255,159,47,.42))}.connection-editor__node--expanded .connection-editor__node-ring{box-shadow:0 0 0 2px #22d3eebd,0 0 34px #22d3ee3d}.connection-editor__node--rotating .connection-editor__node-ring{box-shadow:0 0 0 2px #facc15c7,0 0 42px #facc1547;animation:node-focus-breathe 1.1s ease-in-out infinite}.connection-editor__node--rotating .connection-editor__node-segment{fill-opacity:.68;stroke-opacity:1;stroke-width:1.9;cursor:grabbing}.connection-editor__node-title,.connection-editor__node-subtitle{display:block;max-width:92px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center}.connection-editor__node-title{color:#f8fafc;font-size:.82rem;font-weight:800}.connection-editor__node-subtitle{margin-top:4px;color:#a7b4c8;font-size:.62rem;text-transform:uppercase;letter-spacing:.08em}.connection-editor__ports{position:absolute;z-index:30;display:grid;gap:6px;width:104px;opacity:0;pointer-events:none;transform:translateY(3px);transition:opacity .16s ease,transform .16s ease}.connection-editor__ports--visible{opacity:1;pointer-events:auto;transform:translateY(0)}.connection-editor__ports--outputs{z-index:42}.connection-editor__ports--inputs{z-index:36}.connection-editor__segment-popover{position:absolute;z-index:80;width:292px;max-height:320px;overflow:auto;display:grid;gap:10px;padding:14px;border-radius:16px;border:1px solid rgba(148,163,184,.18);background:linear-gradient(135deg,#0f172af5,#110c20f0),#090c16eb;box-shadow:0 28px 72px #0000007a,0 0 48px #a855f71a,inset 0 1px #ffffff0d;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.connection-editor__segment-popover-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding-bottom:2px}.connection-editor__segment-popover-title{color:#94a3b8;font-size:.68rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase}.connection-editor__segment-popover-hint{margin-top:5px;color:#64748b;font-size:.66rem;font-weight:700}.connection-editor__segment-rotation{flex:0 0 auto;min-width:54px;padding:5px 8px;border-radius:999px;border:1px solid rgba(34,211,238,.28);background:#082f4957;color:#7dd3fc;font-size:.66rem;font-weight:900;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;text-align:center;box-shadow:inset 0 0 16px #0ea5e914}.connection-editor__segment-row{display:grid;grid-template-columns:28px 72px minmax(0,1fr) auto;align-items:center;gap:8px;min-height:40px;padding:7px;border-radius:12px;border:1px solid rgba(148,163,184,.12);background:linear-gradient(90deg,#0f172ac7,#0f172a75),#0f172a99;transition:border-color .15s ease,background .15s ease,box-shadow .15s ease,transform .15s ease}.connection-editor__segment-row--input{border-color:#22d3ee42}.connection-editor__segment-row--output{border-color:#f59e0b47}.connection-editor__segment-row--state{border-color:#a855f74d}.connection-editor__segment-row--source,.connection-editor__segment-row--candidate{transform:translate(2px);box-shadow:0 0 22px #f59e0b24,inset 0 0 20px #f59e0b14}.connection-editor__segment-row--compatible{border-color:#22d3ee6b;box-shadow:inset 0 0 18px #22d3ee14}.connection-editor__segment-row--compat-preview{border-color:#f59e0b94;background:linear-gradient(90deg,#f59e0b1f,#0f172a8a),#0f172ab3;box-shadow:0 0 22px #f59e0b1f,inset 0 0 18px #f59e0b14}.connection-editor__segment-row--invalid-drop{border-color:#fb7185b3;background:linear-gradient(90deg,#f43f5e24,#0f172a94),#0f172ab3;box-shadow:0 0 24px #f43f5e2e,inset 0 0 18px #f43f5e1a;animation:invalid-drop-jolt .36s ease-out}.connection-editor__segment-index{color:#64748b;font-size:.64rem;font-weight:900;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;letter-spacing:.08em}.connection-editor__segment-kind{display:inline-flex;align-items:center;gap:6px;color:#94a3b8;font-size:.62rem;font-weight:800;text-transform:uppercase;min-width:0;white-space:nowrap}.connection-editor__segment-kind-dot{width:7px;height:7px;border-radius:999px;background:#94a3b8;box-shadow:0 0 12px #94a3b847}.connection-editor__segment-row--input .connection-editor__segment-kind-dot{background:#22d3ee;box-shadow:0 0 12px #22d3ee61}.connection-editor__segment-row--output .connection-editor__segment-kind-dot{background:#fb923c;box-shadow:0 0 12px #fb923c66}.connection-editor__segment-row--state .connection-editor__segment-kind-dot{background:#a855f7;box-shadow:0 0 12px #a855f76b}.connection-editor__segment-label{display:inline-flex;align-items:center;gap:5px;min-width:0;overflow:hidden;color:#e2e8f0;font-size:.74rem;font-weight:700;text-overflow:ellipsis;white-space:nowrap}.connection-editor__segment-badge{flex:0 0 auto;padding:2px 5px;border-radius:999px;border:1px solid rgba(56,189,248,.28);background:#0ea5e91a;color:#bae6fd;font-size:.55rem;font-weight:900;text-transform:uppercase}.connection-editor__segment-path{grid-column:3 / 4;min-width:0;margin-top:-5px;overflow:hidden;color:#93c5fd;font-size:.62rem;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;text-overflow:ellipsis;white-space:nowrap}.connection-editor__segment-row--nested{min-height:56px;border-color:#38bdf84d;background:linear-gradient(90deg,#0ea5e91a,#0f172a80),#0f172aa3}.connection-editor__segment-actions{display:inline-flex;gap:4px}.connection-editor__segment-action{width:24px;height:24px;border-radius:999px;border:1px solid rgba(148,163,184,.18);background:#060c16b8;color:#dbeafe;cursor:pointer;font-weight:900;line-height:1}.connection-editor__segment-action:hover{border-color:#22d3ee75;box-shadow:0 0 14px #22d3ee29}.connection-editor__port{min-height:24px;border-radius:999px;border:1px solid rgba(148,163,184,.14);background:#080d18c2;color:#cbd5e1;font-size:.64rem;font-weight:700;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:crosshair;box-shadow:0 8px 20px #00000038}.connection-editor--guided .connection-editor__ports--visible .connection-editor__port:not(.connection-editor__port--guided-visible){display:none}.connection-editor__port--output{border-color:#f59e0b75;color:#fbbf24}.connection-editor__port--input{border-color:#06b6d475;color:#22d3ee}.connection-editor__port--state{border-color:#a855f785;color:#c084fc}.connection-editor__port--compatible{background:#22c55e29;box-shadow:0 0 0 1px #22c55e61,0 0 18px #22c55e2e;animation:snap-pulse 1s ease-in-out infinite}.connection-editor__port--source{border-color:#fb923cb8;background:#fb923c29;color:#fed7aa;box-shadow:0 0 0 1px #fb923c73,0 0 20px #fb923c2e}.connection-editor__port--candidate{background:#22c55e3d;box-shadow:0 0 0 2px #22c55e94,0 0 24px #22c55e3d}.connection-editor__port--incompatible{opacity:.42;cursor:not-allowed;filter:grayscale(.45)}.connection-editor__port--invalid-drop{opacity:1;border-color:#fb7185c7;color:#fecdd3;background:#f43f5e2e;box-shadow:0 0 0 2px #f43f5e75,0 0 24px #f43f5e38;filter:none;animation:invalid-drop-jolt .36s ease-out}.connection-editor__port--connection-focus{border-color:#facc15ad;color:#fde68a;background:#facc151f;box-shadow:0 0 0 1px #facc156b,0 0 20px #facc152e;animation:port-focus-pulse 1.25s ease-in-out infinite}:host-context(.theme-light) .connection-editor__port{color:var(--pce-fg)}.connection-editor__drag-tip{position:absolute;z-index:6;display:grid;gap:4px;width:min(330px,calc(100% - 28px));padding:10px 12px;border-radius:14px;border:1px solid rgba(125,211,252,.26);background:linear-gradient(135deg,#0ea5e91f,#0f172aeb),#080d18f0;box-shadow:0 18px 44px #0206176b,0 0 28px #0ea5e91a;font-size:.76rem;pointer-events:none;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);animation:tooltip-rise .14s ease-out}.connection-editor__drag-tip--valid{border-color:#22c55e66;background:linear-gradient(135deg,#22c55e29,#0f172aeb),#080d18f0;box-shadow:0 18px 44px #0206176b,0 0 30px #22c55e24}.connection-editor__drag-tip--invalid{z-index:7;border-color:#fb71858a;background:linear-gradient(135deg,#f43f5e2e,#0f172af0),#080d18f5;box-shadow:0 18px 44px #0206176b,0 0 34px #f43f5e29;animation:tooltip-rise .14s ease-out,invalid-drop-jolt .36s ease-out}.connection-editor__invalid-drop-pulse{position:absolute;z-index:6;width:16px;height:16px;border-radius:999px;border:2px solid rgba(251,113,133,.88);background:#f43f5e38;box-shadow:0 0 0 8px #f43f5e1a,0 0 28px #f43f5e3d;pointer-events:none;transform:translate(-50%,-50%);animation:invalid-drop-ping 1.2s ease-out both}.connection-editor__drag-tip span{color:#7dd3fc;text-transform:uppercase;letter-spacing:.12em;font-size:.62rem;font-weight:800}.connection-editor__drag-tip strong{min-width:0;overflow:hidden;color:#f8fafc;font-size:.78rem;font-weight:900;text-overflow:ellipsis;white-space:nowrap}.connection-editor__drag-tip small{color:#94a3b8;font-size:.68rem;font-weight:700}.connection-editor__drag-tip--valid small{color:#86efac}.connection-editor__drag-tip--invalid span,.connection-editor__drag-tip--invalid small{color:#fda4af}.connection-editor__summary{grid-column:1;grid-row:2;align-self:end;justify-self:start;z-index:6;display:flex;flex-wrap:wrap;gap:6px;padding:0 0 12px 12px;pointer-events:none}.connection-editor__side-panel{grid-column:2;grid-row:2 / 3;position:relative;z-index:8;min-width:0;min-height:0;display:grid;align-content:start;gap:14px;overflow:auto;padding-inline-end:4px;scrollbar-color:var(--pce-outline-strong) var(--pce-surface-soft)}.connection-editor__side-panel--contextual .connection-editor__inspector{order:-3}.connection-editor__side-panel--contextual .connection-editor__recommended{order:2}.connection-editor__side-panel--contextual .connection-editor__filters-section{order:3}.connection-editor__side-panel--contextual .connection-editor__recommended,.connection-editor__side-panel--contextual .connection-editor__filters-section{opacity:.82}.connection-editor__metric,.connection-editor__item{border-radius:16px;background:var(--pce-surface);border:1px solid var(--pce-outline);box-shadow:inset 0 1px var(--pce-glass-highlight);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px)}.connection-editor__metric{min-width:auto;display:inline-flex;align-items:baseline;gap:7px;padding:6px 9px;border-radius:999px;background:color-mix(in srgb,var(--pce-surface-strong) 86%,transparent);box-shadow:0 10px 26px var(--pce-shadow-soft),inset 0 1px var(--pce-glass-highlight)}.connection-editor__metric span{color:var(--pce-muted);font-size:.62rem;text-transform:uppercase;letter-spacing:.08em}.connection-editor__metric strong,.connection-editor__details-row strong{color:var(--pce-strong);font-size:.78rem}.connection-editor__filters,.connection-editor__actions,.connection-editor__badges{display:flex;gap:8px;flex-wrap:wrap}.connection-editor__filter,.connection-editor__action{border:1px solid var(--pce-outline-strong);background:var(--pce-surface-soft);color:var(--pce-fg);border-radius:999px;padding:6px 10px;font-size:.75rem;font-weight:700;cursor:pointer}.connection-editor__filter--active,.connection-editor__action:hover{border-color:color-mix(in srgb,var(--pce-secondary) 62%,transparent);background:color-mix(in srgb,var(--pce-secondary) 16%,var(--pce-surface-soft));color:var(--pce-strong)}.connection-editor__action--danger{border-color:color-mix(in srgb,var(--pce-error) 48%,transparent);color:var(--pce-error)}.connection-editor__item{width:100%;text-align:start;padding:12px;display:grid;gap:6px;cursor:pointer;color:inherit}.connection-editor__item--active{border-color:#a855f7b8;background:linear-gradient(90deg,#0ea5e91a,#a855f721)}.connection-editor__item-title{font-weight:800;color:#f8fafc;word-break:break-word}.connection-editor__badge{padding:3px 8px;border-radius:999px;background:color-mix(in srgb,var(--pce-primary) 12%,var(--pce-surface));color:var(--pce-fg);font-size:.72rem;font-weight:800}.connection-editor__section{min-width:0;padding:14px;border-radius:18px;background:var(--pce-surface);border:1px solid var(--pce-outline);box-shadow:inset 0 1px var(--pce-glass-highlight),0 18px 50px var(--pce-shadow-soft);-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.connection-editor__badge--warning{background:color-mix(in srgb,var(--pce-warning) 14%,var(--pce-surface));color:var(--pce-fg)}.connection-editor__badge--error{background:color-mix(in srgb,var(--pce-error) 14%,var(--pce-surface));color:var(--pce-fg)}.connection-editor__diagnostics{display:grid;gap:8px}.connection-editor__diagnostic{display:grid;gap:4px;padding:10px;border-radius:12px;border:1px solid var(--pce-outline);background:var(--pce-surface-soft);color:var(--pce-fg);font-size:.78rem;line-height:1.35}.connection-editor__diagnostic--warning{border-color:#f59e0b47;background:#f59e0b1a}.connection-editor__diagnostic--error{border-color:#ef44445c;background:#ef44441f}.connection-editor__diagnostic-severity{font-size:.68rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase;color:var(--pce-fg)}.connection-editor__suggestions{display:grid;gap:10px;margin-top:14px;padding-top:14px;border-top:1px solid var(--pce-outline)}.connection-editor__suggestion{display:grid;gap:8px;width:100%;padding:12px;border-radius:16px;border:1px solid color-mix(in srgb,var(--pce-secondary) 26%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-secondary) 12%,transparent),color-mix(in srgb,var(--pce-primary) 8%,transparent)),var(--pce-surface-strong);color:var(--pce-fg);text-align:start;cursor:pointer;transition:transform .16s ease,border-color .16s ease,background .16s ease}.connection-editor__suggestion:hover{transform:translateY(-1px);border-color:color-mix(in srgb,var(--pce-primary) 46%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-secondary) 18%,transparent),color-mix(in srgb,var(--pce-primary) 12%,transparent)),var(--pce-surface-strong)}.connection-editor__suggestion-route{display:grid;gap:4px;font-size:.78rem}.connection-editor__suggestion-route strong{color:var(--pce-strong);font-size:.82rem}.connection-editor__suggestion-reason{color:var(--pce-muted);font-size:.76rem;line-height:1.35}.connection-editor__suggestion-diagnostic{padding:10px 12px;border-radius:14px;border:1px solid rgba(245,158,11,.28);background:#f59e0b1a;color:var(--pce-strong);font-size:.78rem;line-height:1.35}.connection-editor__suggestion-outcome{color:var(--pce-strong);font-size:.84rem;line-height:1.3}.connection-editor__suggestion-action{justify-self:start;padding:3px 8px;border-radius:999px;background:#22d3ee24;color:#67e8f9;font-size:.68rem;font-weight:900;letter-spacing:.06em;text-transform:uppercase}:host-context(.theme-light) .connection-editor__suggestion-action{background:color-mix(in srgb,var(--pce-primary) 12%,var(--pce-surface-soft));color:#075f73}.connection-editor__trace{display:grid;gap:10px;margin-top:14px;padding-top:14px;border-top:1px solid var(--pce-outline)}.connection-editor__trace-summary{color:var(--pce-muted);font-size:.78rem;line-height:1.35}.connection-editor__trace-step{display:grid;grid-template-columns:24px minmax(76px,auto) 1fr;gap:8px;align-items:center;padding:9px;border-radius:12px;border:1px solid rgba(34,211,238,.18);background:var(--pce-surface-soft);font-size:.76rem}.connection-editor__trace-step--blocked{border-color:#ef444457;background:#ef44441a}.connection-editor__trace-order{display:inline-grid;place-items:center;width:22px;height:22px;border-radius:999px;background:#22d3ee29;color:#67e8f9;font-weight:900}.connection-editor__trace-phase{color:var(--pce-strong);font-weight:900;text-transform:uppercase;letter-spacing:.06em;font-size:.66rem}.connection-editor__trace-copy{min-width:0;color:var(--pce-fg);word-break:break-word}.connection-editor__transform-steps{display:grid;gap:8px}.connection-editor__transform-step{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:8px;align-items:center;padding:9px;border-radius:12px;border:1px solid rgba(168,85,247,.22);background:var(--pce-surface-soft)}.connection-editor__format-fields{grid-column:1 / -1;display:grid;gap:8px;min-width:0}.connection-editor__format-fields label{display:grid;gap:4px}.connection-editor__format-fields input,.connection-editor__format-fields select{width:100%;min-width:0;box-sizing:border-box;padding:8px;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface);border:1px solid var(--md-sys-color-outline);border-radius:4px}.connection-editor__format-fields p{margin:0}.connection-editor__transform-kind{min-width:0;color:var(--pce-strong);font-weight:900;word-break:break-word}.connection-editor__transform-phase{color:var(--pce-muted);font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}.connection-editor__details-row,.connection-editor__details-block{display:grid;gap:6px}.connection-editor__payload-input{width:100%;min-height:128px;box-sizing:border-box;resize:vertical;border:1px solid var(--pce-outline);border-radius:8px;background:color-mix(in srgb,var(--pce-surface) 92%,black);color:var(--pce-fg);padding:10px;font:.76rem/1.45 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}.connection-editor__surface-action-editor{display:block;max-height:min(58vh,620px);overflow:auto;border:1px solid var(--pce-outline);border-radius:8px;background:color-mix(in srgb,var(--pce-surface) 96%,black);padding:10px;margin-bottom:10px}.connection-editor__rule-grid{display:grid;gap:10px}.connection-editor__rule-card{display:grid;gap:8px;padding:10px;border-radius:12px;border:1px solid var(--pce-outline);background:var(--pce-surface-soft)}.connection-editor__rule-heading{color:var(--pce-fg);font-size:.72rem;font-weight:900;text-transform:uppercase;letter-spacing:.06em}pre{margin:0;padding:12px;border-radius:12px;background:color-mix(in srgb,var(--pce-surface-strong) 82%,#020617);color:var(--pce-fg);overflow:auto;font-size:.75rem}@keyframes connection-flow{to{stroke-dashoffset:-38}}@keyframes drag-wire-pulse{0%,to{stroke-opacity:.76}50%{stroke-opacity:1}}@keyframes drag-halo-pulse{0%,to{stroke-opacity:.48}50%{stroke-opacity:.82}}@keyframes endpoint-pulse{0%,to{opacity:.78;transform:scale(1)}50%{opacity:1;transform:scale(1.25)}}@keyframes selected-wire-aura{0%,to{stroke-opacity:.46}50%{stroke-opacity:.92}}@keyframes wire-draw-in{0%{stroke-dashoffset:640;opacity:.32}70%{opacity:1}to{stroke-dashoffset:0;opacity:1}}@keyframes new-wire-halo{0%{stroke-opacity:0;stroke-width:30}28%{stroke-opacity:.78}to{stroke-opacity:0;stroke-width:12}}@keyframes new-dock-link-flash{0%{transform:translateY(-2px);box-shadow:0 16px 40px #0206175c,0 0 42px #fb923c47}to{transform:translateY(0)}}@keyframes trace-wire-pulse{0%,to{stroke-opacity:.78}50%{stroke-opacity:1}}@keyframes trace-halo-pulse{0%,to{stroke-opacity:.34}50%{stroke-opacity:.86}}@keyframes tooltip-rise{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes invalid-drop-jolt{0%,to{transform:translate(0)}28%{transform:translate(-3px)}56%{transform:translate(3px)}}@keyframes invalid-drop-ping{0%{opacity:1;transform:translate(-50%,-50%) scale(.72)}72%{opacity:.74;transform:translate(-50%,-50%) scale(1.35)}to{opacity:0;transform:translate(-50%,-50%) scale(1.8)}}@keyframes node-breathe{50%{transform:scale(1.035)}}@keyframes node-focus-breathe{0%,to{transform:scale(1)}50%{transform:scale(1.055)}}@keyframes trace-source-pulse{50%{transform:scale(1.04);box-shadow:0 0 0 3px #facc15db,0 0 0 22px #facc1514,0 0 64px #facc1557}}@keyframes trace-source-orbit{to{transform:rotate(360deg)}}@keyframes trace-reacting-pulse{50%{transform:scale(1.045);box-shadow:0 0 0 3px #22c55ead,0 0 62px #22c55e4d}}@keyframes segment-focus-sweep{0%,to{stroke-opacity:.82}50%{stroke-opacity:1}}@keyframes snap-pulse{50%{filter:brightness(1.16)}}@keyframes port-focus-pulse{50%{transform:translateY(-1px);box-shadow:0 0 0 2px #facc158f,0 0 26px #facc153d}}.connection-editor__link-list{order:-2}.connection-editor__item-title{color:var(--pce-fg);font-weight:650;overflow-wrap:anywhere}.connection-editor__link-list .connection-editor__item{border:0;border-radius:6px;background:transparent;border-bottom:1px solid var(--pce-outline);box-shadow:none;padding:12px 4px;gap:4px}.connection-editor__link-list .connection-editor__item--active{background:var(--pce-surface-strong)}.connection-editor--guided .connection-editor__widget-list,.connection-editor--guided .connection-editor__link-list .connection-editor__item-copy{display:none}.connection-editor__filters-section,.connection-editor__side-panel--contextual .connection-editor__inspector{order:-3}.connection-editor__working-note{order:4}.connection-editor__draft{order:-5}.connection-editor--guided .connection-editor__segment-kind-legend{background:var(--pce-surface-strong);color:var(--pce-fg)}.connection-editor__node-caption{position:absolute;left:50%;top:-16px;transform:translate(-50%,-100%);color:var(--pce-fg);background:var(--pce-surface-base);border:1px solid var(--pce-outline);border-radius:8px;padding:6px 8px;line-height:1.3;font-weight:650;white-space:normal;overflow-wrap:anywhere}.connection-editor__node-core{padding:8px}.connection-editor__node-icon{font-size:30px}.connection-editor__node-subtitle{display:none}.connection-editor__wire:not(.connection-editor__wire--halo){stroke:var(--pce-primary);stroke-opacity:.85;stroke-width:2;vector-effect:non-scaling-stroke;stroke-dasharray:none;animation:none;filter:none}.connection-editor__wire--state:not(.connection-editor__wire--halo){stroke:var(--pce-tertiary)}.connection-editor__wire--state-read:not(.connection-editor__wire--halo){stroke:var(--pce-secondary)}.connection-editor__wire--error:not(.connection-editor__wire--halo){stroke:var(--pce-error)}.connection-editor__wire--active:not(.connection-editor__wire--halo),.connection-editor__wire--trace:not(.connection-editor__wire--halo){stroke-width:3;stroke-opacity:1;stroke-dasharray:12 6;animation:connection-flow 1.8s linear infinite}.connection-editor__wire--mode-dimmed:not(.connection-editor__wire--halo){stroke-opacity:.16}.connection-editor__header{position:relative;padding-right:58px;gap:12px;flex-wrap:wrap;border-radius:18px}.connection-editor__close{position:absolute;top:8px;right:14px}.connection-editor__mode-frame{min-width:max-content;flex:1 0 auto}.connection-editor__mode-shell{max-width:100%;overflow-x:auto;box-sizing:border-box}.connection-editor__header-actions{flex:0 1 auto}@media(max-width:1400px){.connection-editor__eyebrow{display:none}}@media(min-width:901px)and (max-width:1200px){.connection-editor__mode-frame{order:2;flex-basis:100%}.connection-editor__header-actions{margin-inline-start:auto}}@media(prefers-reduced-motion:reduce){.connection-editor__viewport,.connection-editor__node,.connection-editor__node-ring,.connection-editor__node-core,.connection-editor__node-segment,.connection-editor__dock-link,.connection-editor__port{transition-duration:1ms}.connection-editor__wire,.connection-editor__wire--state,.connection-editor__wire--projection,.connection-editor__wire--state-read,.connection-editor__wire--event-propagation,.connection-editor__wire--halo.connection-editor__wire--active,.connection-editor__wire--trace,.connection-editor__wire--halo.connection-editor__wire--trace,.connection-editor__wire-endpoint,.connection-editor__connection-tooltip,.connection-editor__dock-link--new,.connection-editor__trace-overlay,.connection-editor__trace-overlay-step,.connection-editor__drag-wire,.connection-editor__drag-wire--halo,.connection-editor__drag-endpoint,.connection-editor__drag-tip,.connection-editor__node-ring,.connection-editor__node--connection-focus .connection-editor__node-ring,.connection-editor__node--trace-source .connection-editor__node-ring,.connection-editor__node--trace-source:before,.connection-editor__node--trace-reacting .connection-editor__node-ring,.connection-editor__node--rotating .connection-editor__node-ring,.connection-editor__node-segment--connection-focus,.connection-editor__node-segment--compatible,.connection-editor__node-segment--invalid-drop,.connection-editor__segment-popover,.connection-editor__port--compatible,.connection-editor__port--invalid-drop,.connection-editor__port--connection-focus,.connection-editor__invalid-drop,.connection-editor__invalid-drop-pulse{animation:none}.connection-editor__wire{stroke-dashoffset:0}.connection-editor__wire--new{stroke-dasharray:10 8;stroke-dashoffset:0;opacity:1}.connection-editor__wire--halo.connection-editor__wire--new{stroke-width:12;stroke-opacity:.16}.connection-editor__wire-endpoint,.connection-editor__drag-endpoint,.connection-editor__node-ring,.connection-editor__node--trace-source:before{transform:none}.connection-editor__invalid-drop-pulse{opacity:0}}@media(max-width:900px){:host{inset:0}.connection-editor{inset:8px;height:calc(100vh - 16px);padding:12px;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(260px,1fr) minmax(150px,30vh) minmax(76px,112px)}.connection-editor--dock-collapsed{grid-template-rows:auto minmax(260px,1fr) minmax(150px,30vh) 48px}.connection-editor__header,.connection-editor__stage,.connection-editor__summary,.connection-editor__side-panel,.connection-editor__dock{grid-column:1}.connection-editor__header{align-items:stretch;flex-wrap:wrap;border-radius:18px}.connection-editor__mode-frame{order:3;width:100%;max-width:100%}.connection-editor__close{margin-inline-start:auto}.connection-editor__mode-frame:before,.connection-editor__mode-frame:after{content:\"\";position:absolute;z-index:2;inset-block:4px;width:24px;border-radius:999px;pointer-events:none}.connection-editor__mode-frame:before{inset-inline-start:1px;background:linear-gradient(90deg,var(--pce-surface-base),transparent)}.connection-editor__mode-frame:after{inset-inline-end:1px;background:linear-gradient(270deg,var(--pce-surface-base),transparent)}.connection-editor__mode-shell{width:100%;max-width:100%;overflow-x:auto;scroll-padding-inline:28px;-webkit-overflow-scrolling:touch}.connection-editor__subtitle{margin-inline-start:auto}.connection-editor__stage{grid-row:2;min-height:260px}.connection-editor__summary{grid-row:2;max-width:calc(100% - 24px)}.connection-editor__side-panel{grid-row:3;max-height:30vh}.connection-editor__dock{grid-row:4;min-height:0}.connection-editor__connection-tooltip{width:min(620px,calc(100% - 24px));max-height:min(260px,calc(100% - 24px));overflow:auto}.connection-editor__dock-tabs{width:100%;min-width:0;min-height:0;flex-wrap:wrap;align-items:flex-start}.connection-editor__dock-tablist{width:100%;flex-wrap:wrap}.connection-editor__dock-count{flex:1 1 100%;margin-inline-start:0}.connection-editor__dock-body{width:100%;min-width:0;max-height:76px;box-sizing:border-box}.connection-editor__dock-body pre{white-space:pre-wrap;overflow-wrap:anywhere}.connection-editor__dock-link{grid-template-columns:1fr;align-items:start}.connection-editor__dock-link-route{flex-wrap:wrap}.connection-editor__dock-link-badges{justify-content:flex-start}.connection-editor__trace-overlay{inset-inline-start:14px;width:min(420px,calc(100% - 28px));min-width:0;transform:none}.connection-editor__drag-tip{width:min(300px,calc(100% - 28px))}.connection-editor__segment-popover{width:min(280px,calc(100% - 28px));max-height:min(280px,calc(100% - 28px))}.connection-editor__minimap{inset-inline-end:10px;inset-block-end:10px;width:132px;height:94px;padding:16px 8px 8px;border-radius:14px}.connection-editor__minimap:before{inset:20px 8px 8px;background-size:18px 18px}.connection-editor__minimap-title{inset-block-start:6px;inset-inline-start:10px;font-size:.54rem}}@media(max-width:640px){.connection-editor{inset:4px;height:calc(100vh - 8px);padding:8px;gap:8px;grid-template-rows:auto minmax(240px,1fr) minmax(140px,28vh) minmax(72px,104px)}.connection-editor--dock-collapsed{grid-template-rows:auto minmax(240px,1fr) minmax(140px,28vh) 46px}.connection-editor__header{gap:8px;padding:10px}.connection-editor__mode-shell{padding:4px;gap:2px}.connection-editor__mode{min-height:28px;gap:4px;padding:0 7px;font-size:.68rem}.connection-editor__mode-glyph{font-size:.64rem}.connection-editor__stage{min-height:240px}.connection-editor__zoom-controls{inset-inline-start:8px;inset-block-start:8px;gap:6px;width:42px}.connection-editor__tool-group,.connection-editor__trace-source{gap:6px;padding:8px;border-radius:12px}.connection-editor__tool-group{grid-template-columns:28px}.connection-editor__tool-group:nth-of-type(2){grid-template-columns:28px}.connection-editor__tool-button{width:28px;height:28px}.connection-editor__arrange-button,.connection-editor__simulate-button{width:30px;min-height:30px}.connection-editor__summary{gap:6px;padding-inline-end:8px}.connection-editor__side-panel{max-height:28vh}.connection-editor__dock{border-radius:16px}.connection-editor__dock-tabs,.connection-editor__dock-tablist{gap:4px}.connection-editor__dock-tab{padding:0 9px}.connection-editor__dock-count{font-size:.62rem}.connection-editor__dock-body{max-height:66px;padding:8px}.connection-editor__minimap{width:116px;height:84px}.connection-editor__segment-row{grid-template-columns:24px minmax(54px,.55fr) minmax(0,1fr) auto}}.connection-editor__ports{width:max-content;visibility:hidden}.connection-editor__ports--visible{visibility:visible}.connection-editor__port{font-size:inherit;white-space:normal;overflow:visible;overflow-wrap:anywhere;padding:4px 8px}.connection-editor__header-actions{display:flex;flex-wrap:wrap;gap:8px}.connection-editor__action--primary{background:var(--pce-primary);color:var(--pce-surface-base);font-weight:700}.connection-editor__action:disabled{opacity:.5;cursor:default}.connection-editor__draft{order:-4;padding:16px;border-bottom:1px solid var(--pce-outline)}.connection-editor__draft p,.connection-editor__working-note{font-size:.8rem;line-height:1.55;color:var(--pce-muted)}.connection-editor__working-note{margin:12px 16px}.connection-editor__draft label{display:grid;gap:6px;margin:14px 0;font-size:.82rem}.connection-editor__draft select,.connection-editor__draft input{box-sizing:border-box;width:100%;min-width:0;min-height:40px;padding:8px;color:var(--pce-fg);background:var(--pce-surface-base);border:1px solid var(--pce-outline-strong);border-radius:8px}.connection-editor__draft-review{display:grid;gap:6px;padding:12px 0;font-size:.85rem;overflow-wrap:anywhere}.connection-editor__draft-actions{position:sticky;bottom:0;z-index:4;padding:12px 0;background:var(--pce-surface-base);border-top:1px solid var(--pce-outline)}.connection-editor--draft .connection-editor__filters-section,.connection-editor--draft .connection-editor__link-list{display:none}.connection-editor__draft-status{color:var(--pce-primary)}.connection-editor__advanced{margin:16px 0}.connection-editor__advanced summary{cursor:pointer;font-size:.82rem;padding:10px 0}.connection-editor__node-title{white-space:normal;overflow:visible;text-overflow:clip;overflow-wrap:anywhere;line-height:1.25}.connection-editor--presentation{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr)}.connection-editor--presentation .connection-editor__side-panel,.connection-editor--presentation .connection-editor__summary,.connection-editor--presentation .connection-editor__dock,.connection-editor--presentation .connection-editor__subtitle,.connection-editor--presentation .connection-editor__mode-frame{display:none}.connection-editor--presentation .connection-editor__stage{grid-column:1;grid-row:2}@media(max-width:900px){.connection-editor__header{flex-wrap:wrap}.connection-editor__header-actions{order:2;width:100%}.connection-editor--draft{grid-template-rows:auto minmax(160px,.45fr) minmax(260px,1fr) 46px}.connection-editor--draft .connection-editor__stage{min-height:0}.connection-editor__side-panel{max-height:none}.connection-editor--draft .connection-editor__summary{display:none}}@media(max-width:640px){.connection-editor__eyebrow,.connection-editor__subtitle,.connection-editor__summary,.connection-editor__minimap{display:none}.connection-editor__header{position:relative;padding-right:42px}.connection-editor__close{position:absolute;top:8px;right:8px}.connection-editor__header-actions{flex-wrap:nowrap}.connection-editor__header-actions .connection-editor__action{min-height:36px}.connection-editor__mode-frame{flex-basis:auto}.connection-editor__side-panel{max-height:none}.connection-editor__draft{padding:10px}.connection-editor__stage{overflow:hidden}.connection-editor__connection-tooltip{display:none}.connection-editor__dock{min-width:0}.connection-editor__dock-tablist{min-width:0;overflow-x:auto;max-width:100%}.connection-editor__dock-meta{display:none}}@media(prefers-reduced-motion:reduce){.connection-editor *,.connection-editor *:before,.connection-editor *:after{animation:none!important;transition:none!important}}.connection-editor__terminal{fill:none;stroke:var(--pce-fg);stroke-width:1.6;stroke-linecap:round;opacity:.72;pointer-events:none}.connection-editor__terminal--state{stroke:var(--pce-tertiary)}.connection-editor__terminal--read{stroke:var(--pce-secondary)}.connection-editor__terminal--dimmed{opacity:.14}.connection-editor .connection-editor__segment-rotation{color:var(--pce-fg);background:var(--pce-surface-base);box-shadow:none}.connection-editor__flow-legend{position:absolute;top:12px;right:12px;z-index:85;max-width:calc(100% - 80px);background:var(--md-sys-color-surface-container, var(--pce-surface-base));color:var(--pce-fg);padding:8px 12px;border:1px solid var(--pce-outline);border-radius:10px;font-size:12px}.connection-editor__flow-legend summary{cursor:pointer}.connection-editor__flow-legend>span{display:flex;align-items:center;gap:10px;margin-top:10px}.connection-editor__flow-legend svg{width:48px;height:12px}.connection-editor .connection-editor__wire--state-read:not(.connection-editor__wire--halo){stroke-dasharray:8 4}.connection-editor .connection-editor__wire--event-propagation:not(.connection-editor__wire--halo){stroke-dasharray:2 4}.connection-editor .connection-editor__wire--new:not(.connection-editor__wire--halo){stroke-dashoffset:0}.connection-editor{grid-template-columns:minmax(0,1fr)}.connection-editor__side-panel{display:none}.connection-editor--inspector-open:not(.connection-editor--presentation) .connection-editor__side-panel{display:grid;grid-column:1;grid-row:2;justify-self:end;align-self:stretch;width:min(380px,100%);max-height:100%;padding:12px;box-sizing:border-box;background:var(--md-sys-color-surface-container, var(--pce-surface-base));border:1px solid var(--pce-outline-strong);border-radius:12px;z-index:90;box-shadow:0 12px 32px var(--pce-shadow-soft)}.connection-editor .connection-editor__segment-popover{z-index:100;box-sizing:border-box;transform-origin:top left;padding:12px;background:var(--md-sys-color-surface-container, var(--pce-surface-base));color:var(--pce-fg);border-color:var(--pce-outline-strong);-webkit-backdrop-filter:none;backdrop-filter:none;box-shadow:0 12px 32px var(--pce-shadow-soft);overscroll-behavior:contain}.connection-editor .connection-editor__segment-popover-header{position:sticky;top:-12px;padding:8px 0;z-index:1;background:var(--md-sys-color-surface-container, var(--pce-surface-base))}.connection-editor .connection-editor__segment-row{grid-template-columns:22px 64px minmax(0,1fr) auto;background:var(--md-sys-color-surface-container-low, var(--pce-surface-base));color:var(--pce-fg);border-color:var(--pce-outline);box-shadow:none}.connection-editor .connection-editor__segment-row .connection-editor__port-select{color:var(--pce-fg);white-space:normal;overflow-wrap:anywhere;font-size:12px}.connection-editor__port-select{min-width:0;font:inherit;border:0;background:transparent;text-align:start;cursor:pointer;padding:4px}.connection-editor .connection-editor__segment-action{background:var(--md-sys-color-surface-container-high, var(--pce-surface-base));color:var(--pce-fg);box-shadow:none}.connection-editor .connection-editor__segment-kind,.connection-editor .connection-editor__segment-index,.connection-editor .connection-editor__segment-popover-hint{color:var(--pce-muted)}.connection-editor .connection-editor__segment-popover-title{color:var(--pce-fg)}.connection-editor .connection-editor__port{background:var(--md-sys-color-surface-container, var(--pce-surface-base));color:var(--pce-fg);box-shadow:none}.connection-editor__port-select:focus-visible,.connection-editor__segment-action:focus-visible{outline:2px solid var(--pce-primary);outline-offset:2px}.connection-editor .connection-editor__wire:not(.connection-editor__wire--trace),.connection-editor .connection-editor__node:hover .connection-editor__node-ring,.connection-editor .connection-editor__node:focus-within .connection-editor__node-ring,.connection-editor .connection-editor__node--expanded .connection-editor__node-ring{animation:none}.connection-editor .connection-editor__wire:not(.connection-editor__wire--halo,.connection-editor__wire--mode-dimmed){stroke-opacity:.8}@media(max-width:900px){.connection-editor{grid-template-rows:auto minmax(0,1fr) minmax(0,32vh)}.connection-editor .connection-editor__mode-frame{min-width:0;flex:1 1 100%}.connection-editor.connection-editor--dock-collapsed{grid-template-rows:auto minmax(0,1fr) 48px}.connection-editor .connection-editor__dock{grid-row:3;grid-template-rows:48px minmax(0,1fr)}.connection-editor .connection-editor__dock--collapsed{grid-template-rows:48px}.connection-editor .connection-editor__dock-tabs{box-sizing:border-box;flex-wrap:nowrap;align-items:center;padding:6px 8px}.connection-editor .connection-editor__dock-tablist{flex:1 1 0;width:auto;flex-wrap:nowrap;overflow-x:auto}.connection-editor .connection-editor__dock-tab,.connection-editor .connection-editor__dock-toggle{flex-shrink:0}.connection-editor .connection-editor__dock-count{display:none}.connection-editor .connection-editor__dock-body{min-height:0;max-height:none}.connection-editor .connection-editor__dock-link-row{box-sizing:border-box;grid-template-columns:minmax(0,1fr)}.connection-editor .connection-editor__dock-link-chip{white-space:normal;overflow-wrap:anywhere}.connection-editor .connection-editor__stage{min-height:0}.connection-editor.connection-editor--presentation{grid-template-rows:auto minmax(0,1fr)}}.connection-editor__node{--pce-node-accent: hsl(var(--p-connection-node-hue, 210) 58% 44%)}.connection-editor .connection-editor__node-ring{background:radial-gradient(circle,transparent 57%,color-mix(in srgb,var(--pce-node-accent) 28%,var(--pce-surface-base)) 58% 85%,var(--pce-node-accent) 86%);box-shadow:none;animation:none}.connection-editor .connection-editor__node-core{background:color-mix(in srgb,var(--pce-node-accent) 18%,var(--pce-surface-base));color:var(--pce-fg);border:3px solid var(--pce-node-accent);box-shadow:none;text-shadow:none}.connection-editor .connection-editor__node-icon{color:inherit;filter:none}.connection-editor .connection-editor__node-caption{border-inline-start:5px solid var(--pce-node-accent);transform:translate(-50%,-50%);box-sizing:border-box;display:flex;align-items:center;justify-content:center;overflow:hidden}.connection-editor .connection-editor__node-caption>span{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}.connection-editor .connection-editor__connection-tooltip{z-index:88;box-sizing:border-box;max-height:220px;overflow:hidden;background:var(--pce-surface-base);color:var(--pce-fg);-webkit-backdrop-filter:none;backdrop-filter:none;animation:none}.connection-editor .connection-editor__connection-tooltip-row strong{white-space:normal;overflow-wrap:anywhere}.connection-editor .connection-editor__connection-tooltip-callout{display:none}.connection-editor--guided .connection-editor__node-segment{opacity:.12}.connection-editor--guided .connection-editor__node-halo,.connection-editor--guided .connection-editor__node-anchor,.connection-editor--guided .connection-editor__node-rotate-handle,.connection-editor--guided .connection-editor__segment-rotation{display:none}.connection-editor__port-inspection{grid-column:1 / -1;min-width:0;padding:8px}.connection-editor__port-inspection p{margin:0 0 8px;line-height:1.5;color:var(--pce-fg)}.connection-editor__port-connection,.connection-editor .connection-editor__port-create{display:block;width:100%;min-height:36px;padding:8px;margin-block:4px;text-align:start;white-space:normal;overflow-wrap:anywhere;height:auto;color:var(--pce-fg);background:var(--pce-surface-base);border:1px solid var(--pce-outline);border-radius:8px;cursor:pointer}.connection-editor__port-select:focus-visible,.connection-editor__port-connection:focus-visible,.connection-editor__port-create:focus-visible{outline:2px solid var(--pce-primary);outline-offset:2px}.connection-editor.connection-editor--tracing{grid-template-rows:auto auto minmax(0,1fr) minmax(0,28%)}.connection-editor.connection-editor--tracing.connection-editor--dock-collapsed{grid-template-rows:auto auto minmax(0,1fr) 48px}.connection-editor.connection-editor--tracing.connection-editor--presentation{grid-template-rows:auto auto minmax(0,1fr)}.connection-editor.connection-editor--tracing .connection-editor__stage,.connection-editor.connection-editor--tracing .connection-editor__side-panel{grid-row:3}.connection-editor.connection-editor--tracing .connection-editor__summary{display:none}.connection-editor.connection-editor--tracing .connection-editor__dock{grid-row:4}.connection-editor .connection-editor__trace-overlay{position:relative;grid-row:2;grid-column:1 / -1;inset:auto;transform:none;box-sizing:border-box;width:100%;min-width:0;max-height:210px;overflow:auto;padding:10px 14px;background:var(--pce-surface-base);color:var(--pce-fg);border-color:var(--pce-outline);box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none;animation:none}.connection-editor .connection-editor__trace-overlay-head span,.connection-editor .connection-editor__trace-overlay-route strong{color:var(--pce-fg);font-size:13px;white-space:normal;overflow-wrap:anywhere}.connection-editor .connection-editor__trace-overlay-head strong{color:var(--pce-fg);white-space:nowrap;flex-shrink:0}.connection-editor__trace-explanation{font-size:12px;color:var(--pce-muted);margin:6px 0;line-height:1.4}.connection-editor .connection-editor__trace-overlay-button,.connection-editor .connection-editor__trace-overlay-step{width:32px;height:32px;min-width:32px;flex-shrink:0;font-size:12px;color:var(--pce-fg);background:var(--pce-surface-soft);border-color:var(--pce-outline)}.connection-editor .connection-editor__trace-overlay-step--active{background:color-mix(in srgb,var(--pce-primary) 24%,var(--pce-surface-base));color:var(--pce-fg)}.connection-editor__trace-technical{margin-top:8px;font-size:12px}.connection-editor__trace-technical summary{cursor:pointer}.connection-editor__trace-technical span{display:inline-block;margin:8px 12px 0 0}.connection-editor__compact{height:100%;overflow:auto;padding:12px;box-sizing:border-box;touch-action:pan-y;color:var(--pce-fg)}.connection-editor__compact>p{margin:0 0 12px;color:var(--pce-muted);line-height:1.5;font-size:13px}.connection-editor__compact-node{border-bottom:1px solid var(--pce-outline);padding:6px 0}.connection-editor__compact-node summary{display:flex;align-items:center;gap:10px;min-height:48px;cursor:pointer}.connection-editor__compact-node summary strong{flex:1;min-width:0;overflow-wrap:anywhere;font-size:14px}.connection-editor__compact-node .connection-editor__node-icon{color:var(--pce-fg);border-inline-start:4px solid hsl(var(--p-connection-node-hue) 58% 44%);padding:6px;font-size:24px}.connection-editor .connection-editor__compact-edge--active{border-inline-start:4px solid var(--pce-primary);font-weight:650;background:color-mix(in srgb,var(--pce-primary) 12%,var(--pce-surface-base))}.connection-editor__compact-count{font-size:12px;color:var(--pce-muted)}.connection-editor__compact-node p{font-size:13px}.connection-editor__compact-edge,.connection-editor__compact-ports{display:block;box-sizing:border-box;width:100%;padding:12px;margin:6px 0;min-height:44px;color:var(--pce-fg);background:var(--pce-surface-base);border:1px solid var(--pce-outline);border-radius:8px;text-align:start;font:inherit;font-size:13px;white-space:normal;overflow-wrap:anywhere;cursor:pointer}.connection-editor__compact-edge small{display:block;margin-top:6px;color:var(--pce-muted)}.connection-editor__compact :focus-visible,.connection-editor__trace-overlay :focus-visible{outline:2px solid var(--pce-primary);outline-offset:2px}.connection-editor__stage{display:grid;grid-template-rows:minmax(0,1fr) auto;overflow:hidden}.connection-editor__map-surface{position:relative;min-width:0;min-height:0;overflow:clip;isolation:isolate}.connection-editor__canvas-controls{display:grid;grid-template-columns:minmax(0,1fr) 116px;align-items:center;gap:6px 12px;padding:6px 10px;border-top:1px solid var(--pce-outline);background:var(--pce-surface-base);cursor:auto;touch-action:auto;min-width:0}.connection-editor__canvas-tools{display:flex;align-items:center;gap:16px;min-width:0;overflow-x:auto;padding:3px}.connection-editor__canvas-controls .connection-editor__zoom-controls{position:static;display:flex;align-items:center;width:auto;flex:0 0 auto}.connection-editor__canvas-controls .connection-editor__tool-group{display:flex;padding:3px;gap:4px;box-shadow:none}.connection-editor__canvas-controls .connection-editor__arrange-button{opacity:1;transform:none;color:var(--pce-fg)}.connection-editor__canvas-controls .connection-editor__arrange-button span{color:var(--pce-primary)}.connection-editor__canvas-controls .connection-editor__flow-legend{position:static;flex:0 0 auto;max-width:none;padding:4px 8px;box-shadow:none}.connection-editor__canvas-controls .connection-editor__flow-legend>span{display:inline-flex;margin:6px 12px 0 0;gap:5px}.connection-editor__canvas-controls .connection-editor__flow-legend svg{width:30px}.connection-editor__canvas-controls .connection-editor__summary{position:static;display:flex;align-self:center;flex:0 0 auto;gap:8px;margin:0;padding:0;max-width:none}.connection-editor__canvas-controls .connection-editor__metric{display:grid;gap:2px;text-align:center;min-width:0;padding:0;background:none;border:0;box-shadow:none}.connection-editor__canvas-controls .connection-editor__minimap{position:relative;inset:auto;display:block;width:116px;height:64px;padding:18px 6px 4px;opacity:1;transform:none;box-shadow:none;border-radius:8px}.connection-editor__canvas-controls .connection-editor__trace-source{grid-column:1 / -1;display:flex;align-items:center;overflow-x:auto;max-height:44px;padding:3px;box-shadow:none;white-space:nowrap}.connection-editor__canvas-controls .connection-editor__trace-source>*{flex:0 0 auto}.connection-editor__map-surface:has(.connection-editor__compact)>.connection-editor__viewport,.connection-editor__stage:has(.connection-editor__compact) .connection-editor__canvas-tools,.connection-editor__stage:has(.connection-editor__compact) .connection-editor__minimap,.connection-editor__stage:has(.connection-editor__compact)>.connection-editor__canvas-controls:not(:has(.connection-editor__trace-source)){display:none}.connection-editor__stage:has(.connection-editor__compact)>.connection-editor__canvas-controls{grid-template-columns:minmax(0,1fr)}.connection-editor__stage:has(.connection-editor__compact){touch-action:pan-y;cursor:auto;background:var(--pce-stage-bg)}\n"] }]
12859
13218
  }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], focusWidget: [{ type: i0.Output, args: ["focusWidget"] }], openPageSettings: [{ type: i0.Output, args: ["openPageSettings"] }], close: [{ type: i0.Output, args: ["close"] }] } });
12860
13219
 
13220
+ /** Internal presentation of the existing catalog. Never rearranges or persists a page. */
13221
+ class PagePresetComparisonComponent {
13222
+ expanded = input(false, ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
13223
+ layoutId = input('', ...(ngDevMode ? [{ debugName: "layoutId" }] : /* istanbul ignore next */ []));
13224
+ themeId = input('', ...(ngDevMode ? [{ debugName: "themeId" }] : /* istanbul ignore next */ []));
13225
+ layoutPresetOptions = input(...(ngDevMode ? [undefined, { debugName: "layoutPresetOptions" }] : /* istanbul ignore next */ []));
13226
+ canvas = input(false, ...(ngDevMode ? [{ debugName: "canvas" }] : /* istanbul ignore next */ []));
13227
+ choose = output();
13228
+ presets = Object.values(BUILTIN_PAGE_LAYOUT_PRESETS);
13229
+ i18n = inject(PraxisI18nService);
13230
+ effectiveLayout = computed(() => resolveWidgetPageLayoutPreset({ layoutPreset: this.layoutId(), layoutPresetOptions: this.layoutPresetOptions() }), ...(ngDevMode ? [{ debugName: "effectiveLayout" }] : /* istanbul ignore next */ []));
13231
+ suggestedTheme = computed(() => {
13232
+ const id = this.effectiveLayout()?.defaultThemePreset;
13233
+ return id ? BUILTIN_PAGE_THEME_PRESETS[id] : undefined;
13234
+ }, ...(ngDevMode ? [{ debugName: "suggestedTheme" }] : /* istanbul ignore next */ []));
13235
+ effectiveTheme = computed(() => this.themeId() ? BUILTIN_PAGE_THEME_PRESETS[this.themeId()] : this.suggestedTheme(), ...(ngDevMode ? [{ debugName: "effectiveTheme" }] : /* istanbul ignore next */ []));
13236
+ t(key, fallback) { return resolvePraxisPageBuilderText(this.i18n, 'presets.' + key, fallback); }
13237
+ groupLabel(group) { return this.t('slot.' + group.id, 'label' in group ? group.label || group.id : group.id); }
13238
+ presetLabel(id, fallback) { return this.t('layout.' + id, fallback); }
13239
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PagePresetComparisonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13240
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PagePresetComparisonComponent, isStandalone: true, selector: "praxis-page-preset-comparison", inputs: { expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, layoutId: { classPropertyName: "layoutId", publicName: "layoutId", isSignal: true, isRequired: false, transformFunction: null }, themeId: { classPropertyName: "themeId", publicName: "themeId", isSignal: true, isRequired: false, transformFunction: null }, layoutPresetOptions: { classPropertyName: "layoutPresetOptions", publicName: "layoutPresetOptions", isSignal: true, isRequired: false, transformFunction: null }, canvas: { classPropertyName: "canvas", publicName: "canvas", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { choose: "choose" }, ngImport: i0, template: `
13241
+ <ng-template #context>
13242
+ <section class="preset-context" [attr.aria-label]="t('scope', 'Effect of this choice')">
13243
+ <strong>{{ t('scope', 'Effect of this choice') }}</strong>
13244
+ <p>{{ canvas() ? t('canvas', 'Canvas positions are preserved. This preset does not move or resize existing widgets.') : t('flow', 'Explicit layout and grouping take precedence over preset defaults. Selecting a preset does not create widgets or data connections.') }}</p>
13245
+ @if (effectiveLayout(); as layout) {
13246
+ @if (layout.id !== layoutId().trim()) {
13247
+ <p>{{ t('inheritedLayout', 'Inherited organization') }}: <strong>{{ presetLabel(layout.id, layout.label) }}</strong></p>
13248
+ }
13249
+ }
13250
+ <p class="theme" data-testid="preset-effective-theme"><span>{{ t('effectiveTheme', 'Effective page theme') }}</span><strong>{{ effectiveTheme()?.label || themeId() || t('hostTheme', 'Host theme') }}</strong>
13251
+ <span>{{ themeId() ? t('explicit', 'Explicit choice') : effectiveTheme() ? t('inherited', 'Inherited from layout') : t('host', 'No page theme override') }}</span>
13252
+ </p>
13253
+ @if (suggestedTheme(); as theme) {
13254
+ @if (themeId() && themeId() !== theme.id) {
13255
+ <p>{{ t('suggested', 'Suggested by the layout') }}: {{ theme.label }} · {{ t('notActive', 'not active') }}</p>
13256
+ }
13257
+ }
13258
+ </section>
13259
+ </ng-template>
13260
+ @if (!expanded()) { <ng-container [ngTemplateOutlet]="context" /> }
13261
+ <details class="comparison" [open]="expanded()" [class.expanded]="expanded()">
13262
+ <summary>{{ t('compare', 'Compare page organizations') }} · {{ presets.length }}</summary>
13263
+ <p class="hint">{{ t('diagramHelp', 'Role diagrams, not a preview of your page. Compare the structure, then select an organization. Existing widgets and connections are preserved.') }}</p>
13264
+ <div class="choices">
13265
+ @for (preset of presets; track preset.id) {
13266
+ <article [class.selected]="effectiveLayout()?.id === preset.id" [attr.data-layout-preset]="preset.id">
13267
+ <header><strong>{{ presetLabel(preset.id, preset.label) }}</strong>@if (effectiveLayout()?.id === preset.id) { <span>{{ t('selected', 'Selected') }}</span> }</header>
13268
+ <ul class="diagram" [attr.aria-label]="t('roles', 'Organization roles')">
13269
+ @for (group of preset.defaultGrouping || []; track group.id) {
13270
+ <li class="role" [class.wide]="group.kind === 'hero' || group.kind === 'tabs'" [class.rail]="group.kind === 'rail'">
13271
+ {{ groupLabel(group) }}
13272
+ </li>
13273
+ }
13274
+ </ul>
13275
+ <p>{{ t('use.' + preset.id, preset.description || preset.label) }}</p>
13276
+ <ul>
13277
+ @for (slot of preset.slotModel; track slot.id) {
13278
+ @if (slot.required) { <li>{{ t('slot.' + slot.id, slot.label) }} · {{ t('required', 'required role') }}</li> }
13279
+ }
13280
+ </ul>
13281
+ <button type="button" [attr.aria-pressed]="effectiveLayout()?.id === preset.id" (click)="choose.emit(preset.id)">{{ t('choose', 'Choose') }} {{ presetLabel(preset.id, preset.label) }}</button>
13282
+ </article>
13283
+ }
13284
+ </div>
13285
+ <p class="hint">{{ t('mobile', 'Mobile preferences describe the intended composition. Verify the actual responsive layout; selection alone does not hide content, create tabs or connect master and detail.') }}</p>
13286
+ </details>
13287
+ @if (expanded()) { <ng-container [ngTemplateOutlet]="context" /> }
13288
+ `, isInline: true, styles: [":host{display:block;container-type:inline-size;color:var(--md-sys-color-on-surface)}p{margin:var(--p-space-2,8px) 0;line-height:1.5}.preset-context{border-inline-start:3px solid var(--md-sys-color-primary);padding:var(--p-space-3,12px);background:var(--md-sys-color-surface-container-low)}.theme{display:flex;flex-wrap:wrap;gap:var(--p-space-2,8px)}.theme span,.hint,li{color:var(--md-sys-color-on-surface-variant);font-size:var(--p-font-size-sm,12px)}.expanded>summary{display:none}summary{cursor:pointer;padding-block:var(--p-space-3,12px);font-weight:600}.choices{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-3,12px)}article{padding:var(--p-space-3,12px);border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--p-radius-md,8px);display:flex;flex-direction:column;min-width:0}article.selected{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low)}header{display:flex;flex-wrap:wrap;justify-content:space-between;gap:var(--p-space-2,8px)}header span{font-size:var(--p-font-size-sm,12px);color:var(--md-sys-color-primary)}.diagram{list-style:none;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-1,4px);margin-block:var(--p-space-3,12px)}.role{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);padding:var(--p-space-2,8px);font-size:var(--p-font-size-sm,12px);overflow-wrap:anywhere}.wide{grid-column:1 / -1}.rail{border-inline-start:3px solid var(--md-sys-color-primary)}ul{padding-inline-start:var(--p-space-4,16px)}button{margin-top:auto;min-height:44px;padding:var(--p-space-2,8px);border:1px solid var(--md-sys-color-outline);border-radius:var(--p-radius-sm,4px);background:var(--md-sys-color-surface);color:var(--md-sys-color-primary);font:inherit;cursor:pointer;text-align:start}button:hover{background:var(--md-sys-color-surface-container)}button:focus-visible,summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:3px}@container (max-width:520px){.choices{grid-template-columns:minmax(0,1fr)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13289
+ }
13290
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PagePresetComparisonComponent, decorators: [{
13291
+ type: Component,
13292
+ args: [{ selector: 'praxis-page-preset-comparison', imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: `
13293
+ <ng-template #context>
13294
+ <section class="preset-context" [attr.aria-label]="t('scope', 'Effect of this choice')">
13295
+ <strong>{{ t('scope', 'Effect of this choice') }}</strong>
13296
+ <p>{{ canvas() ? t('canvas', 'Canvas positions are preserved. This preset does not move or resize existing widgets.') : t('flow', 'Explicit layout and grouping take precedence over preset defaults. Selecting a preset does not create widgets or data connections.') }}</p>
13297
+ @if (effectiveLayout(); as layout) {
13298
+ @if (layout.id !== layoutId().trim()) {
13299
+ <p>{{ t('inheritedLayout', 'Inherited organization') }}: <strong>{{ presetLabel(layout.id, layout.label) }}</strong></p>
13300
+ }
13301
+ }
13302
+ <p class="theme" data-testid="preset-effective-theme"><span>{{ t('effectiveTheme', 'Effective page theme') }}</span><strong>{{ effectiveTheme()?.label || themeId() || t('hostTheme', 'Host theme') }}</strong>
13303
+ <span>{{ themeId() ? t('explicit', 'Explicit choice') : effectiveTheme() ? t('inherited', 'Inherited from layout') : t('host', 'No page theme override') }}</span>
13304
+ </p>
13305
+ @if (suggestedTheme(); as theme) {
13306
+ @if (themeId() && themeId() !== theme.id) {
13307
+ <p>{{ t('suggested', 'Suggested by the layout') }}: {{ theme.label }} · {{ t('notActive', 'not active') }}</p>
13308
+ }
13309
+ }
13310
+ </section>
13311
+ </ng-template>
13312
+ @if (!expanded()) { <ng-container [ngTemplateOutlet]="context" /> }
13313
+ <details class="comparison" [open]="expanded()" [class.expanded]="expanded()">
13314
+ <summary>{{ t('compare', 'Compare page organizations') }} · {{ presets.length }}</summary>
13315
+ <p class="hint">{{ t('diagramHelp', 'Role diagrams, not a preview of your page. Compare the structure, then select an organization. Existing widgets and connections are preserved.') }}</p>
13316
+ <div class="choices">
13317
+ @for (preset of presets; track preset.id) {
13318
+ <article [class.selected]="effectiveLayout()?.id === preset.id" [attr.data-layout-preset]="preset.id">
13319
+ <header><strong>{{ presetLabel(preset.id, preset.label) }}</strong>@if (effectiveLayout()?.id === preset.id) { <span>{{ t('selected', 'Selected') }}</span> }</header>
13320
+ <ul class="diagram" [attr.aria-label]="t('roles', 'Organization roles')">
13321
+ @for (group of preset.defaultGrouping || []; track group.id) {
13322
+ <li class="role" [class.wide]="group.kind === 'hero' || group.kind === 'tabs'" [class.rail]="group.kind === 'rail'">
13323
+ {{ groupLabel(group) }}
13324
+ </li>
13325
+ }
13326
+ </ul>
13327
+ <p>{{ t('use.' + preset.id, preset.description || preset.label) }}</p>
13328
+ <ul>
13329
+ @for (slot of preset.slotModel; track slot.id) {
13330
+ @if (slot.required) { <li>{{ t('slot.' + slot.id, slot.label) }} · {{ t('required', 'required role') }}</li> }
13331
+ }
13332
+ </ul>
13333
+ <button type="button" [attr.aria-pressed]="effectiveLayout()?.id === preset.id" (click)="choose.emit(preset.id)">{{ t('choose', 'Choose') }} {{ presetLabel(preset.id, preset.label) }}</button>
13334
+ </article>
13335
+ }
13336
+ </div>
13337
+ <p class="hint">{{ t('mobile', 'Mobile preferences describe the intended composition. Verify the actual responsive layout; selection alone does not hide content, create tabs or connect master and detail.') }}</p>
13338
+ </details>
13339
+ @if (expanded()) { <ng-container [ngTemplateOutlet]="context" /> }
13340
+ `, styles: [":host{display:block;container-type:inline-size;color:var(--md-sys-color-on-surface)}p{margin:var(--p-space-2,8px) 0;line-height:1.5}.preset-context{border-inline-start:3px solid var(--md-sys-color-primary);padding:var(--p-space-3,12px);background:var(--md-sys-color-surface-container-low)}.theme{display:flex;flex-wrap:wrap;gap:var(--p-space-2,8px)}.theme span,.hint,li{color:var(--md-sys-color-on-surface-variant);font-size:var(--p-font-size-sm,12px)}.expanded>summary{display:none}summary{cursor:pointer;padding-block:var(--p-space-3,12px);font-weight:600}.choices{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-3,12px)}article{padding:var(--p-space-3,12px);border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--p-radius-md,8px);display:flex;flex-direction:column;min-width:0}article.selected{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low)}header{display:flex;flex-wrap:wrap;justify-content:space-between;gap:var(--p-space-2,8px)}header span{font-size:var(--p-font-size-sm,12px);color:var(--md-sys-color-primary)}.diagram{list-style:none;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-1,4px);margin-block:var(--p-space-3,12px)}.role{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);padding:var(--p-space-2,8px);font-size:var(--p-font-size-sm,12px);overflow-wrap:anywhere}.wide{grid-column:1 / -1}.rail{border-inline-start:3px solid var(--md-sys-color-primary)}ul{padding-inline-start:var(--p-space-4,16px)}button{margin-top:auto;min-height:44px;padding:var(--p-space-2,8px);border:1px solid var(--md-sys-color-outline);border-radius:var(--p-radius-sm,4px);background:var(--md-sys-color-surface);color:var(--md-sys-color-primary);font:inherit;cursor:pointer;text-align:start}button:hover{background:var(--md-sys-color-surface-container)}button:focus-visible,summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:3px}@container (max-width:520px){.choices{grid-template-columns:minmax(0,1fr)}}\n"] }]
13341
+ }], propDecorators: { expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }], layoutId: [{ type: i0.Input, args: [{ isSignal: true, alias: "layoutId", required: false }] }], themeId: [{ type: i0.Input, args: [{ isSignal: true, alias: "themeId", required: false }] }], layoutPresetOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "layoutPresetOptions", required: false }] }], canvas: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvas", required: false }] }], choose: [{ type: i0.Output, args: ["choose"] }] } });
13342
+
12861
13343
  /** Preserve layout semantics while clearing an authored inset back to the host token. */
12862
13344
  function withPagePaddingInline(layout, value) {
12863
13345
  const next = { ...layout };
@@ -13063,11 +13545,122 @@ const THEME_PRESET_ENTRIES = Object.values(BUILTIN_PAGE_THEME_PRESETS).map((pres
13063
13545
  const SHELL_PRESET_ENTRIES = BUILTIN_WIDGET_SHELL_PRESETS;
13064
13546
  const integerColumnValidator = (control) => control.value == null || Number.isInteger(control.value) ? null : { integer: true };
13065
13547
  class DynamicPageConfigEditorComponent {
13066
- set page(value) { this.pageInput = value ?? null; this.hydrate(); }
13548
+ set page(value) { this.pageInput = value ?? null; this.hydrate(true); }
13067
13549
  layout;
13068
13550
  presets;
13069
13551
  pagePreset;
13070
13552
  pageChange = new EventEmitter();
13553
+ arrangementFeedback;
13554
+ focusArrangementFeedback = false;
13555
+ workspaceTab = signal(0, ...(ngDevMode ? [{ debugName: "workspaceTab" }] : /* istanbul ignore next */ []));
13556
+ arrangementSelection = signal('', ...(ngDevMode ? [{ debugName: "arrangementSelection" }] : /* istanbul ignore next */ []));
13557
+ // Transient authoring choices, expressed through the existing Core width contract.
13558
+ arrangementChoices = [
13559
+ { id: 'stack', weights: [1] }, { id: 'compare', weights: [1, 1] },
13560
+ { id: 'primary', weights: [2, 1] }, { id: 'secondary', weights: [1, 2] },
13561
+ { id: 'grid', weights: [1, 1, 1] }, { id: 'center', weights: [1, 2, 1] },
13562
+ { id: 'wide-left', weights: [3, 1] }, { id: 'wide-right', weights: [1, 3] },
13563
+ { id: 'four', weights: [1, 1, 1, 1] },
13564
+ ];
13565
+ arrangementWidths(id) {
13566
+ const weights = this.arrangementChoices.find(choice => choice.id === id)?.weights || [1];
13567
+ const columns = this.canvasColumnsControl.value || 0;
13568
+ if (!Number.isInteger(columns) || columns < 1)
13569
+ return weights.map(() => 0);
13570
+ const total = weights.reduce((sum, value) => sum + value, 0);
13571
+ const quotas = weights.map(weight => columns * weight / total);
13572
+ const widths = quotas.map(Math.floor);
13573
+ // Apportion indivisible columns by fractional remainder, keeping balanced lanes
13574
+ // within one column and favoring the wider lane when asymmetric quotas tie.
13575
+ const ranking = quotas.map((quota, index) => ({ index, remainder: quota - widths[index] }))
13576
+ .sort((a, b) => b.remainder - a.remainder || weights[b.index] - weights[a.index] || b.index - a.index);
13577
+ const remaining = columns - widths.reduce((sum, value) => sum + value, 0);
13578
+ for (let index = 0; index < remaining; index++)
13579
+ widths[ranking[index].index]++;
13580
+ return widths;
13581
+ }
13582
+ arrangementFits(id) { return this.arrangementWidths(id).every(width => Number.isInteger(width) && width > 0); }
13583
+ themeTokens(id) { return BUILTIN_PAGE_THEME_PRESETS[id]?.tokens || {}; }
13584
+ shellName(id) { const preset = this.shellPresetEntries.find(item => item.id === id); return preset ? this.shellPresetLabel(preset) : id; }
13585
+ shellDescription(preset) { return this.i18n.resolve(preset.description, ''); }
13586
+ previewRowCount(before, after) {
13587
+ return Math.max(1, ...Object.values(before.items).map(item => item.row + item.rowSpan - 1), ...Object.values(after.items).map(item => item.row + item.rowSpan - 1));
13588
+ }
13589
+ previewItems(canvas) {
13590
+ const source = this.arrangementPreview()?.before || canvas;
13591
+ return Object.keys(source.items).sort((a, b) => source.items[a].row - source.items[b].row || source.items[a].col - source.items[b].col || a.localeCompare(b)).map((key, index) => ({
13592
+ ...canvas.items[key], key, number: index + 1,
13593
+ label: this.canvasLimits.targets.find(target => target.scope === 'base' && target.key === key)?.title || key,
13594
+ }));
13595
+ }
13596
+ arrangementPreview = signal(null, ...(ngDevMode ? [{ debugName: "arrangementPreview" }] : /* istanbul ignore next */ []));
13597
+ arrangementMessage = signal('', ...(ngDevMode ? [{ debugName: "arrangementMessage" }] : /* istanbul ignore next */ []));
13598
+ arrangementUndoAvailable = signal(false, ...(ngDevMode ? [{ debugName: "arrangementUndoAvailable" }] : /* istanbul ignore next */ []));
13599
+ arrangedItems;
13600
+ arrangementAppliedDocument = '';
13601
+ arrangementSourcePage = null;
13602
+ arrangementGeometry = '';
13603
+ previousArrangementItems;
13604
+ prepareArrangement(id) {
13605
+ this.arrangementPreview.set(null);
13606
+ const page = this.buildCurrentPage(true);
13607
+ if (!page)
13608
+ return;
13609
+ this.arrangementSelection.set('');
13610
+ const widths = this.arrangementWidths(id);
13611
+ const proposal = prepareCanvasArrangement(page, widths);
13612
+ this.focusArrangementFeedback = true;
13613
+ if (!proposal.canvas) {
13614
+ this.arrangementMessage.set(this.tx('editor.arrange.' + proposal.reason, 'Review the canvas before arranging widgets.')
13615
+ + (proposal.blockedKeys.length ? ' · ' + proposal.blockedKeys.join(', ') : ''));
13616
+ return;
13617
+ }
13618
+ this.arrangementSelection.set(id);
13619
+ this.arrangementMessage.set(proposal.changedKeys.length ? '' : this.tx('editor.arrange.unchanged', 'This arrangement already matches the page.'));
13620
+ if (proposal.changedKeys.length)
13621
+ this.arrangementPreview.set({ source: this.draftFingerprint(), before: page.canvas, canvas: proposal.canvas, keys: proposal.changedKeys });
13622
+ }
13623
+ useArrangement() {
13624
+ const preview = this.arrangementPreview();
13625
+ if (!preview)
13626
+ return;
13627
+ if (preview.source !== this.draftFingerprint()) {
13628
+ this.arrangementPreview.set(null);
13629
+ this.arrangementMessage.set(this.tx('editor.arrange.stale', 'The draft changed. Prepare the arrangement again.'));
13630
+ return;
13631
+ }
13632
+ const current = this.buildCurrentPage(true);
13633
+ if (!current?.canvas) {
13634
+ this.arrangementPreview.set(null);
13635
+ this.arrangementMessage.set(this.tx('editor.arrange.stale', 'The draft changed. Prepare the arrangement again.'));
13636
+ return;
13637
+ }
13638
+ this.arrangementSourcePage = this.clone(current);
13639
+ this.previousArrangementItems = this.clone(current.canvas.items);
13640
+ this.arrangedItems = preview.canvas.items;
13641
+ this.arrangementGeometry = this.geometryFingerprint(preview.canvas);
13642
+ this.arrangementUndoAvailable.set(true);
13643
+ this.arrangementPreview.set(null);
13644
+ this.arrangementMessage.set(this.tx('editor.arrange.staged', 'Arrangement added to the draft. Use Apply to preview it on the page.'));
13645
+ this.refreshPanelState();
13646
+ this.arrangementAppliedDocument = JSON.stringify(this.buildCurrentPage(false));
13647
+ }
13648
+ undoArrangement() {
13649
+ if (!this.arrangementUndoAvailable())
13650
+ return;
13651
+ this.arrangementSelection.set('');
13652
+ const current = this.arrangedItems ?? this.currentPageInput()?.canvas?.items ?? {};
13653
+ const restored = Object.fromEntries(Object.entries(current).map(([key, item]) => {
13654
+ const previous = this.previousArrangementItems?.[key];
13655
+ return [key, previous ? { ...item, col: previous.col, row: previous.row,
13656
+ colSpan: previous.colSpan, rowSpan: previous.rowSpan } : item];
13657
+ }));
13658
+ this.arrangedItems = JSON.stringify(restored) === JSON.stringify(this.currentPageInput()?.canvas?.items) ? undefined : restored;
13659
+ this.arrangementUndoAvailable.set(false);
13660
+ this.arrangementPreview.set(null);
13661
+ this.arrangementMessage.set(this.tx('editor.arrange.undone', 'The previous arrangement is restored in the draft. Use Apply to update the page.'));
13662
+ this.refreshPanelState();
13663
+ }
13071
13664
  layoutPresetEntries = LAYOUT_PRESET_ENTRIES;
13072
13665
  themePresetEntries = THEME_PRESET_ENTRIES;
13073
13666
  shellPresetEntries = SHELL_PRESET_ENTRIES;
@@ -13110,7 +13703,14 @@ class DynamicPageConfigEditorComponent {
13110
13703
  renderedValidationFingerprint = '';
13111
13704
  tabOrder = ['canvas', 'grouping', 'devices', 'context', 'state', 'limits'];
13112
13705
  constructor() {
13113
- afterEveryRender({ read: () => this.refreshRenderedValidation() });
13706
+ this.injectedData?.appliedValues$?.pipe(takeUntilDestroyed()).subscribe((value) => this.acceptAppliedValue(value));
13707
+ afterEveryRender({ write: () => {
13708
+ if (this.focusArrangementFeedback && this.arrangementFeedback) {
13709
+ this.focusArrangementFeedback = false;
13710
+ this.arrangementFeedback.nativeElement.focus({ preventScroll: true });
13711
+ this.arrangementFeedback.nativeElement.scrollIntoView({ block: 'nearest' });
13712
+ }
13713
+ }, read: () => this.refreshRenderedValidation() });
13114
13714
  merge(this.canvasLimits.form.valueChanges, this.canvasColumnsControl.valueChanges, this.canvasRowUnitControl.valueChanges, this.canvasGapControl.valueChanges, this.paddingInlineControl.valueChanges, this.canvasAutoRowsControl.valueChanges, this.layoutPresetControl.valueChanges, this.themePresetControl.valueChanges, this.shellPresetControl.valueChanges, this.contextControl.valueChanges, this.stateControl.valueChanges, this.groupingNodes.valueChanges, this.deviceForms.desktop.valueChanges, this.deviceForms.tablet.valueChanges, this.deviceForms.mobile.valueChanges).pipe(takeUntilDestroyed()).subscribe(() => { if (!this.hydrating)
13115
13715
  this.refreshPanelState(); });
13116
13716
  }
@@ -13136,13 +13736,27 @@ class DynamicPageConfigEditorComponent {
13136
13736
  }
13137
13737
  get selectedLayoutPresetLabel() { return this.tx('editor.summary.layout', 'Layout: {{label}}').replace('{{label}}', this.lookupPresetLabel(this.layoutPresetEntries, this.layoutPresetControl.value, this.tx('editor.fallback.manual', 'manual'))); }
13138
13738
  get selectedThemePresetLabel() { return this.tx('editor.summary.theme', 'Theme: {{label}}').replace('{{label}}', this.lookupPresetLabel(this.themePresetEntries, this.themePresetControl.value, this.tx('editor.layout.layoutDefault', 'Layout default'))); }
13139
- get selectedLayoutPresetEntry() { return this.layoutPresetEntries.find((entry) => entry.id === this.layoutPresetControl.value) || null; }
13739
+ get selectedLayoutPresetEntry() { const id = resolveWidgetPageLayoutPreset({ layoutPreset: this.layoutPresetControl.value, layoutPresetOptions: this.layoutPresetOptions })?.id; return this.layoutPresetEntries.find(entry => entry.id === id) || null; }
13140
13740
  get selectedThemePresetEntry() { return this.themePresetEntries.find((entry) => entry.id === this.themePresetControl.value) || null; }
13141
13741
  get recommendedThemePresetEntry() { const themeId = this.selectedLayoutPresetEntry?.defaultThemePreset; return themeId ? this.themePresetEntries.find((entry) => entry.id === themeId) || null : null; }
13142
13742
  get canvasSummaryLabel() { return this.tx('editor.summary.canvasColumns', 'Canvas: {{count}} columns').replace('{{count}}', String(this.canvasColumnsControl.value)); }
13143
- onSelectedTabIndexChange(index) { this.activeTab.set(this.tabOrder[index] || 'canvas'); }
13743
+ focusEditorTab(tab) { this.workspaceTab.set(3); this.activeTab.set(tab); }
13744
+ selectDetailTab(index) { this.activeTab.set(this.tabOrder[index] || 'canvas'); }
13745
+ onSelectedTabIndexChange(index) { this.focusEditorTab(this.tabOrder[index] || 'canvas'); }
13144
13746
  getSettingsValue() { return this.buildCurrentPage(true); }
13145
13747
  onSave() { return this.getSettingsValue(); }
13748
+ acceptAppliedValue(value) {
13749
+ // Only the document owner acknowledges successful Apply. Merely reading the
13750
+ // provider for a preview must never advance its baseline.
13751
+ if (!value || JSON.stringify(value) !== JSON.stringify(this.buildCurrentPage(false)))
13752
+ return;
13753
+ const accepted = this.buildCurrentPage(false);
13754
+ if (this.arrangementUndoAvailable() && this.geometryFingerprint(accepted.canvas) === this.arrangementGeometry) {
13755
+ this.arrangementAppliedDocument = JSON.stringify(value);
13756
+ }
13757
+ this.pageInput = JSON.stringify(value);
13758
+ this.hydrate(true);
13759
+ }
13146
13760
  reset() { this.hydrate(); }
13147
13761
  pinRecommendedThemePreset() { const theme = this.recommendedThemePresetEntry; if (theme)
13148
13762
  this.themePresetControl.setValue(theme.id); }
@@ -13189,7 +13803,7 @@ class DynamicPageConfigEditorComponent {
13189
13803
  preset.preferTabsOnMobile ? this.tx('editor.preview.prefersMobileTabs', 'prefers mobile tabs') : '',
13190
13804
  preset.hideSecondaryContentOnMobile ? this.tx('editor.preview.hidesSecondaryContent', 'hides secondary content') : '',
13191
13805
  ].filter(Boolean);
13192
- return policies.join(' · ') || this.tx('editor.preview.noDevicePolicy', 'No specific mobile policy');
13806
+ return this.tx('presets.declaredPolicy', 'Declared preference, not an automatic transformation') + ': ' + (policies.join(' · ') || this.tx('editor.preview.noDevicePolicy', 'No specific mobile policy'));
13193
13807
  }
13194
13808
  inheritedThemeHelp(theme) { return this.themePresetControl.value ? theme.description : this.tx('editor.preview.inheritedThemeHelp', '{{description}} This theme is inherited automatically while the theme field stays on layout default.').replace('{{description}}', theme.description); }
13195
13809
  groupLabel(index) { return this.tx('editor.grouping.groupN', 'Group {{index}}').replace('{{index}}', String(index + 1)); }
@@ -13219,7 +13833,7 @@ class DynamicPageConfigEditorComponent {
13219
13833
  this.layoutPresetControl.value, this.themePresetControl.value, this.shellPresetControl.value,
13220
13834
  this.contextControl.value, this.stateControl.value, this.groupingNodes.getRawValue(),
13221
13835
  this.deviceKeys.map(key => this.deviceForms[key].getRawValue()),
13222
- this.canvasLimits.form.getRawValue(), [...this.stackedDevices],
13836
+ this.canvasLimits.form.getRawValue(), [...this.stackedDevices], this.arrangedItems,
13223
13837
  ]);
13224
13838
  }
13225
13839
  limitsGeometryLabel() {
@@ -13238,10 +13852,26 @@ class DynamicPageConfigEditorComponent {
13238
13852
  const form = target && target.scope !== 'base' ? this.deviceForms[target.scope] : undefined;
13239
13853
  return (form?.controls.autoRows.value || this.canvasAutoRowsControl.value) === 'content';
13240
13854
  }
13855
+ geometryFingerprint(canvas) {
13856
+ return JSON.stringify([canvas?.columns, Object.entries(canvas?.items ?? {}).sort(([a], [b]) => a.localeCompare(b))
13857
+ .map(([key, item]) => [key, item.col, item.row, item.colSpan, item.rowSpan])]);
13858
+ }
13241
13859
  applyCanvasLimits(page, reportErrors) {
13242
13860
  this.renderedCandidate = null;
13243
13861
  const issue = this.canvasLimits.apply(page);
13244
13862
  if (!issue) {
13863
+ if (this.arrangementSourcePage) {
13864
+ const arrangementIssue = validateCanvasArrangement(page, this.arrangementSourcePage);
13865
+ if (arrangementIssue) {
13866
+ this.canvasError.set(this.tx('editor.arrange.' + arrangementIssue.reason, 'Review the canvas before arranging widgets.')
13867
+ + (arrangementIssue.blockedKeys.length ? ' · ' + arrangementIssue.blockedKeys.join(', ') : ''));
13868
+ if (reportErrors) {
13869
+ this.focusEditorTab('canvas');
13870
+ this.isValid$.next(false);
13871
+ }
13872
+ return null;
13873
+ }
13874
+ }
13245
13875
  this.limitsError.set(null);
13246
13876
  // Optional transient runtime validation supplied through SETTINGS_PANEL_DATA.
13247
13877
  // The canonical output remains the page only, never the validator/measurements.
@@ -13259,7 +13889,7 @@ class DynamicPageConfigEditorComponent {
13259
13889
  this.limitsTargetControl.setValue(index);
13260
13890
  }
13261
13891
  if (reportErrors) {
13262
- this.activeTab.set('limits');
13892
+ this.focusEditorTab('limits');
13263
13893
  this.isValid$.next(false);
13264
13894
  }
13265
13895
  return null;
@@ -13274,7 +13904,7 @@ class DynamicPageConfigEditorComponent {
13274
13904
  this.limitsError.set(this.limitsTargetLabel(issue.index) + ': ' + messages[issue.code]);
13275
13905
  if (reportErrors) {
13276
13906
  this.limitsTargetControl.setValue(issue.index);
13277
- this.activeTab.set('limits');
13907
+ this.focusEditorTab('limits');
13278
13908
  this.isValid$.next(false);
13279
13909
  }
13280
13910
  return null;
@@ -13294,7 +13924,13 @@ class DynamicPageConfigEditorComponent {
13294
13924
  }
13295
13925
  createDeviceVariantForm() { return new FormGroup({ columns: new FormControl(null, { validators: [Validators.min(1), integerColumnValidator] }), rowUnit: new FormControl('', { nonNullable: true }), gap: new FormControl('', { nonNullable: true }), paddingInline: new FormControl('', { nonNullable: true }), autoRows: new FormControl('', { nonNullable: true }), groupingOverrides: new FormControl('', { nonNullable: true }), widgetOverrides: new FormControl('', { nonNullable: true }) }); }
13296
13926
  createGroupingNodeGroup(seed) { const label = seed && 'label' in seed && typeof seed.label === 'string' ? seed.label : ''; return new FormGroup({ kind: new FormControl(seed?.kind || 'section', { nonNullable: true }), id: new FormControl(seed?.id || '', { nonNullable: true, validators: [Validators.required] }), label: new FormControl(label, { nonNullable: true }), widgetKeysCsv: new FormControl(this.stringifyGroupingWidgetKeys(seed), { nonNullable: true }), layout: new FormControl(seed?.kind === 'section' ? seed.layout || '' : '', { nonNullable: true }), emphasis: new FormControl(seed?.kind === 'hero' ? seed.emphasis || '' : '', { nonNullable: true }), side: new FormControl(seed?.kind === 'rail' ? seed.side || 'right' : 'right', { nonNullable: true }), tabsJson: new FormControl(seed?.kind === 'tabs' ? JSON.stringify(seed.tabs || [], null, 2) : '', { nonNullable: true }) }); }
13297
- hydrate() { this.stackedDevices.clear(); const page = this.currentPageInput() || { widgets: [] }; const canvas = page.canvas || this.defaultCanvas(); const context = this.clone(page.context || {}); this.hydrating = true; this.canvasLimits.hydrate(page); this.limitsTargetControl.setValue(0, { emitEvent: false }); this.limitsError.set(null); this.canvasColumnsControl.setValue(canvas.columns || 12, { emitEvent: false }); this.canvasRowUnitControl.setValue(canvas.rowUnit || '80px', { emitEvent: false }); this.canvasGapControl.setValue(canvas.gap || '16px', { emitEvent: false }); this.paddingInlineControl.setValue(page.layout?.paddingInline || '', { emitEvent: false }); this.canvasAutoRowsControl.setValue(canvas.autoRows || 'fixed', { emitEvent: false }); this.layoutPresetControl.setValue(page.layoutPreset || '', { emitEvent: false }); this.themePresetControl.setValue(page.themePreset || '', { emitEvent: false }); this.shellPresetControl.setValue(this.extractShellPreset(context) || this.pagePreset || '', { emitEvent: false }); this.replaceGroupingNodes(page.grouping); this.contextControl.setValue(this.stringify(this.removeShellPresetFromContext(context)), { emitEvent: false }); this.stateControl.setValue(this.stringify(page.state || {}), { emitEvent: false }); for (const device of this.deviceKeys) {
13927
+ hydrate(preserveEcho = false) { this.arrangementSelection.set(''); this.focusArrangementFeedback = false; const keepUndo = preserveEcho && this.arrangementUndoAvailable() && this.arrangementAppliedDocument === JSON.stringify(this.currentPageInput()); const undoItems = this.previousArrangementItems; this.arrangementPreview.set(null); this.arrangementMessage.set(''); this.arrangedItems = undefined; this.previousArrangementItems = undefined; this.arrangementUndoAvailable.set(keepUndo); if (keepUndo)
13928
+ this.previousArrangementItems = undoItems;
13929
+ else {
13930
+ this.arrangementAppliedDocument = '';
13931
+ this.arrangementSourcePage = null;
13932
+ this.arrangementGeometry = '';
13933
+ } this.stackedDevices.clear(); const page = this.currentPageInput() || { widgets: [] }; const canvas = page.canvas || this.defaultCanvas(); const context = this.clone(page.context || {}); this.hydrating = true; this.canvasLimits.hydrate(page); this.limitsTargetControl.setValue(0, { emitEvent: false }); this.limitsError.set(null); this.canvasColumnsControl.setValue(canvas.columns || 12, { emitEvent: false }); this.canvasRowUnitControl.setValue(canvas.rowUnit || '80px', { emitEvent: false }); this.canvasGapControl.setValue(canvas.gap || '16px', { emitEvent: false }); this.paddingInlineControl.setValue(page.layout?.paddingInline || '', { emitEvent: false }); this.canvasAutoRowsControl.setValue(canvas.autoRows || 'fixed', { emitEvent: false }); this.layoutPresetControl.setValue(page.layoutPreset || '', { emitEvent: false }); this.themePresetControl.setValue(page.themePreset || '', { emitEvent: false }); this.shellPresetControl.setValue(this.extractShellPreset(context) || '', { emitEvent: false }); this.replaceGroupingNodes(page.grouping); this.contextControl.setValue(this.stringify(this.removeShellPresetFromContext(context)), { emitEvent: false }); this.stateControl.setValue(this.stringify(page.state || {}), { emitEvent: false }); for (const device of this.deviceKeys) {
13298
13934
  const variant = page.deviceLayouts?.[device];
13299
13935
  this.deviceForms[device].setValue({ columns: variant?.canvas?.columns ?? null, rowUnit: variant?.canvas?.rowUnit || '', gap: variant?.canvas?.gap || '', paddingInline: variant?.layout?.paddingInline || '', autoRows: variant?.canvas?.autoRows || '', groupingOverrides: this.stringify(variant?.groupingOverrides || []), widgetOverrides: this.stringify(variant?.widgetOverrides || {}) }, { emitEvent: false });
13300
13936
  } this.canvasError.set(null); this.groupingError.set(null); this.deviceError.set(null); this.contextError.set(null); this.stateError.set(null); this.hydrating = false; this.refreshPanelState(true); }
@@ -13305,7 +13941,8 @@ class DynamicPageConfigEditorComponent {
13305
13941
  return null; this.groupingError.set(null); const context = this.parseRecordJson(this.contextControl.value, {}); if (context === null)
13306
13942
  return this.invalidate('context', this.contextError, reportErrors, this.tx('editor.errors.contextInvalid', 'Invalid context')); this.contextError.set(null); const state = this.parseRecordJson(this.stateControl.value, {}); if (state === null)
13307
13943
  return this.invalidate('state', this.stateError, reportErrors, this.tx('editor.errors.stateInvalid', 'Invalid state')); this.stateError.set(null); const deviceLayouts = this.buildDeviceLayouts(reportErrors, next.deviceLayouts); if (deviceLayouts === null)
13308
- return null; next.canvas = { ...(next.canvas || this.defaultCanvas()), mode: 'grid', columns: this.canvasColumnsControl.value, rowUnit: this.canvasRowUnitControl.value.trim() || '80px', gap: this.canvasGapControl.value.trim() || '16px', autoRows: this.canvasAutoRowsControl.value, items: this.completeCanvasItems(next) }; next.layout = withPagePaddingInline(next.layout, this.paddingInlineControl.value); next.layoutPreset = this.layoutPresetControl.value.trim() || undefined; next.themePreset = this.themePresetControl.value.trim() || undefined; next.grouping = grouping.length ? grouping : undefined; next.deviceLayouts = Object.keys(deviceLayouts).length ? deviceLayouts : undefined; next.context = this.applyShellPresetToContext(context, this.shellPresetControl.value.trim()); next.state = Object.keys(state).length ? state : undefined; return this.applyCanvasLimits(next, reportErrors); }
13944
+ return null; next.canvas = this.buildCanvas(next); if (next.canvas && this.arrangedItems)
13945
+ next.canvas = { ...next.canvas, items: this.clone(this.arrangedItems) }; next.layout = withPagePaddingInline(next.layout, this.paddingInlineControl.value); next.layoutPreset = this.layoutPresetControl.value.trim() || undefined; next.themePreset = this.themePresetControl.value.trim() || undefined; next.grouping = grouping.length ? grouping : undefined; next.deviceLayouts = Object.keys(deviceLayouts).length ? deviceLayouts : undefined; next.context = this.buildPageContext(next.context, context); next.state = Object.keys(state).length ? state : undefined; return this.applyCanvasLimits(next, reportErrors); }
13309
13946
  buildGrouping(reportErrors) { const grouping = []; const seenIds = new Set(); for (const group of this.groupingNodes.controls) {
13310
13947
  const kind = group.controls.kind.value;
13311
13948
  const id = group.controls.id.value.trim();
@@ -13335,6 +13972,37 @@ class DynamicPageConfigEditorComponent {
13335
13972
  }
13336
13973
  grouping.push({ kind: 'rail', id, side: (group.controls.side.value || 'right'), widgetKeys });
13337
13974
  } return grouping; }
13975
+ buildPageContext(original, context) {
13976
+ const shellPreset = this.shellPresetControl.value.trim();
13977
+ const untouched = this.contextControl.value === this.stringify(this.removeShellPresetFromContext(original || {}))
13978
+ && shellPreset === this.extractShellPreset(original || {});
13979
+ return untouched ? original : this.applyShellPresetToContext(context, shellPreset);
13980
+ }
13981
+ buildCanvas(page) {
13982
+ const original = page.canvas;
13983
+ const initial = original || this.defaultCanvas();
13984
+ const columns = this.canvasColumnsControl.value;
13985
+ const rowUnit = this.canvasRowUnitControl.value.trim() || '80px';
13986
+ const gap = this.canvasGapControl.value.trim() || '16px';
13987
+ const autoRows = this.canvasAutoRowsControl.value;
13988
+ const columnsChanged = columns !== (initial.columns || 12);
13989
+ const rowUnitChanged = rowUnit !== (initial.rowUnit || '80px');
13990
+ const gapChanged = gap !== (initial.gap || '16px');
13991
+ const autoRowsChanged = autoRows !== (initial.autoRows || 'fixed');
13992
+ // Displayed defaults are not authored overrides. Selecting a reference must
13993
+ // neither freeze defaults nor replace a flow page with a generated canvas.
13994
+ if (!columnsChanged && !rowUnitChanged && !gapChanged && !autoRowsChanged)
13995
+ return original;
13996
+ if (!original)
13997
+ return { ...initial, columns, rowUnit, gap, autoRows, items: this.completeCanvasItems(page) };
13998
+ return {
13999
+ ...original,
14000
+ ...(columnsChanged ? { columns } : {}),
14001
+ ...(rowUnitChanged ? { rowUnit } : {}),
14002
+ ...(gapChanged ? { gap } : {}),
14003
+ ...(autoRowsChanged ? { autoRows } : {}),
14004
+ };
14005
+ }
13338
14006
  /** A grid must place every existing widget before replacing the flow layout. */
13339
14007
  completeCanvasItems(page) {
13340
14008
  const items = { ...(page.canvas?.items || {}) };
@@ -13386,7 +14054,7 @@ class DynamicPageConfigEditorComponent {
13386
14054
  } this.deviceError.set(null); return next; }
13387
14055
  invalidate(tab, target, reportErrors, message) { if (reportErrors) {
13388
14056
  target.set(message);
13389
- this.activeTab.set(tab);
14057
+ this.focusEditorTab(tab);
13390
14058
  this.isValid$.next(false);
13391
14059
  } return null; }
13392
14060
  refreshPanelState(resetSnapshot = false) {
@@ -13394,10 +14062,17 @@ class DynamicPageConfigEditorComponent {
13394
14062
  const current = this.buildCurrentPage(false);
13395
14063
  this.isValid$.next(!!current);
13396
14064
  const fingerprint = this.draftFingerprint();
14065
+ if (this.arrangementPreview() && this.arrangementPreview().source !== fingerprint) {
14066
+ this.arrangementPreview.set(null);
14067
+ this.arrangementSelection.set('');
14068
+ this.focusArrangementFeedback = false;
14069
+ this.arrangementMessage.set(this.tx('editor.arrange.stale', 'The draft changed. Prepare the arrangement again.'));
14070
+ }
13397
14071
  if (resetSnapshot)
13398
14072
  this.snapshot = fingerprint;
13399
14073
  this.isDirty$.next(fingerprint !== this.snapshot);
13400
14074
  }
14075
+ get layoutPresetOptions() { return this.currentPageInput()?.layoutPresetOptions; }
13401
14076
  currentPageInput() { return this.parsePage(this.pageInput) || this.parsePage(this.injectedData?.page || null); }
13402
14077
  parsePage(input) { if (!input)
13403
14078
  return null; if (typeof input === 'string') {
@@ -13455,289 +14130,11 @@ class DynamicPageConfigEditorComponent {
13455
14130
  clone(value) { if (value == null || typeof value !== 'object')
13456
14131
  return value; return JSON.parse(JSON.stringify(value)); }
13457
14132
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicPageConfigEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13458
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DynamicPageConfigEditorComponent, isStandalone: true, selector: "praxis-dynamic-page-config-editor", inputs: { page: "page", layout: "layout", presets: "presets", pagePreset: "pagePreset" }, outputs: { pageChange: "pageChange" }, host: { listeners: { "window:resize": "refreshRenderedValidation()" } }, providers: [providePraxisPageBuilderI18n()], ngImport: i0, template: `
13459
- <div class="editor">
13460
- @if (!isHostedInSettingsPanel) { <div class="head">
13461
- <div>{{ tx('editor.title', 'Page settings') }}</div>
13462
- <button mat-stroked-button type="button" (click)="reset()">{{ tx('editor.reload', 'Reload') }}</button>
13463
- </div> }
13464
- <details class="summary-details">
13465
- <summary>{{ tx('editor.summaryAria', 'Configuration summary') }}</summary>
13466
- <div class="summary">
13467
- @if (!isHostedInSettingsPanel) {
13468
- <span>{{ validationStateLabel }}</span>
13469
- <span>{{ dirtyStateLabel }}</span>
13470
- }
13471
- <span>{{ selectedLayoutPresetLabel }}</span>
13472
- <span>{{ selectedThemePresetLabel }}</span>
13473
- <span>{{ canvasSummaryLabel }}</span>
13474
- </div>
13475
- </details>
13476
- @if (nonLimitError) { <p class="limits-error" role="alert">{{ nonLimitError }}</p> }
13477
- @if (activeTab() === 'limits' && canvasLimits.targets.length) {
13478
- <div class="selection-identity" data-testid="canvas-limits-identity">
13479
- <mat-icon aria-hidden="true">dashboard_customize</mat-icon>
13480
- <strong>{{ limitsTargetLabel(limitsTargetControl.value) }}</strong>
13481
- </div>
13482
- }
13483
- <mat-tab-group class="editor-tabs" [mat-stretch-tabs]="false" [selectedIndex]="selectedTabIndex" (selectedIndexChange)="onSelectedTabIndexChange($event)">
13484
- <mat-tab [label]="tx('editor.tabs.layout', 'Layout')"><div class="grid"><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.cols', 'Columns') }}</mat-label><input matInput type="number" min="1" [formControl]="canvasColumnsControl" /></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.rowUnit', 'Row unit') }}</mat-label><input matInput [formControl]="canvasRowUnitControl" /></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.gap', 'Gap') }}</mat-label><input matInput [formControl]="canvasGapControl" /></mat-form-field><mat-form-field class="page-inset-field" appearance="outline"><mat-label>{{ tx('editor.layout.paddingInline', 'Page side inset') }}</mat-label><input matInput [formControl]="paddingInlineControl" placeholder="24px" /><button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="tx('editor.hints.paddingInline', 'Shared space beside all page widgets. Use a CSS length or theme token; 0px removes it. Leave empty to inherit the host theme.')" [attr.aria-label]="tx('editor.layout.paddingInline', 'Page side inset')"></button></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.autoRows', 'Auto rows') }}</mat-label><mat-select [formControl]="canvasAutoRowsControl"><mat-option value="fixed">{{ tx('editor.layout.autoRowsFixed', 'Fixed height') }}</mat-option><mat-option value="content">{{ tx('editor.layout.autoRowsContent', 'Fit content') }}</mat-option></mat-select></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.layoutPreset', 'Layout preset') }}</mat-label><mat-select [formControl]="layoutPresetControl"><mat-option value="">{{ tx('editor.layout.none', 'None') }}</mat-option>@for (preset of layoutPresetEntries; track preset.id) {<mat-option [value]="preset.id">{{ preset.label }}</mat-option>}</mat-select></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.themePreset', 'Theme preset') }}</mat-label><mat-select [formControl]="themePresetControl"><mat-option value="">{{ tx('editor.layout.layoutDefault', 'Layout default') }}</mat-option>@for (preset of themePresetEntries; track preset.id) {<mat-option [value]="preset.id">{{ preset.label }}</mat-option>}</mat-select></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.shellPreset', 'Shell visual preset') }}</mat-label><mat-select [formControl]="shellPresetControl"><mat-option value="">{{ tx('editor.layout.none', 'None') }}</mat-option>@for (preset of shellPresetEntries; track preset.id) {<mat-option [value]="preset.id">{{ shellPresetLabel(preset) }}</mat-option>}</mat-select></mat-form-field></div>@if (selectedLayoutPresetEntry; as preset) {<div class="preset-preview"><div class="preset-preview__head"><div><span class="eyebrow">{{ tx('editor.preview.layoutEyebrow', 'Layout template') }} · {{ preset.category }}</span><strong>{{ preset.label }}</strong></div><span>{{ layoutPresetSummary(preset) }}</span></div><p>{{ preset.description || tx('editor.preview.layoutFallbackDescription', 'Page organization preset.') }}</p><div class="preset-preview__chips"><span>{{ slotsSummary(preset) }}</span><span>{{ requiredSlotsSummary(preset) }}</span><span>{{ devicePolicySummary(preset) }}</span></div>@if (recommendedThemePresetEntry; as theme) {<div class="preset-preview__recommendation"><div><span class="eyebrow">{{ tx('editor.preview.inheritedTheme', 'Inherited theme') }}</span><strong>{{ theme.label }}</strong><span>{{ inheritedThemeHelp(theme) }}</span></div><button mat-stroked-button type="button" [disabled]="themePresetControl.value === theme.id" (click)="pinRecommendedThemePreset()">{{ tx('editor.preview.pinTheme', 'Pin theme') }}</button></div>}</div>} @if (selectedThemePresetEntry; as theme) {<div class="preset-preview preset-preview--theme"><div class="preset-preview__head"><div><span class="eyebrow">{{ tx('editor.preview.explicitTheme', 'Explicit theme') }}</span><strong>{{ theme.label }}</strong></div><span>{{ theme.density }} · {{ theme.motion }}</span></div><p>{{ theme.description || tx('editor.preview.themeFallbackDescription', 'Page visual preset.') }}</p><div class="preset-preview__chips"><span>{{ tx('editor.preview.shellChip', 'Shell: {{value}}').replace('{{value}}', theme.shellPreset) }}</span><span>{{ tx('editor.preview.chartsChip', 'Charts: {{value}}').replace('{{value}}', theme.chartThemePreset) }}</span></div></div>}</mat-tab>
13485
- <mat-tab [label]="tx('editor.tabs.grouping', 'Grouping')"><div class="panel">
13486
- <p class="hint" data-testid="canvas-grouping-notice">{{ tx('editor.grouping.canvasNotice', 'Canvas positions control this page. Groups below are preserved for flow layouts and do not arrange widgets in the canvas. Use the widget move and resize controls to place them side by side.') }}</p><div class="head"><span>{{ tx('editor.grouping.title', 'Grouping') }}</span><button mat-stroked-button type="button" (click)="addGroupingNode()"><mat-icon>add</mat-icon>{{ tx('editor.grouping.add', 'Add group') }}</button></div>@for (group of groupingNodes.controls; track $index) {<div class="card" [formGroup]="group"><div class="head"><span>{{ groupLabel($index) }}</span><button praxisIconButton="delete" size="compact" type="button" [attr.aria-label]="tx('editor.grouping.removeAria', 'Remove group')" (click)="removeGroupingNode($index)"></button></div><div class="grid"><mat-form-field appearance="outline"><mat-label>{{ tx('editor.grouping.kind', 'Kind') }}</mat-label><mat-select formControlName="kind"><mat-option value="section">section</mat-option><mat-option value="tabs">tabs</mat-option><mat-option value="hero">hero</mat-option><mat-option value="rail">rail</mat-option></mat-select></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.grouping.id', 'Id') }}</mat-label><input matInput formControlName="id" /></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.grouping.label', 'Label') }}</mat-label><input matInput formControlName="label" /></mat-form-field>@if (group.controls.kind.value === 'section') {<mat-form-field appearance="outline"><mat-label>{{ tx('editor.grouping.layout', 'Layout') }}</mat-label><mat-select formControlName="layout"><mat-option value="">{{ tx('editor.grouping.layoutDefault', 'Default') }}</mat-option><mat-option value="stack">stack</mat-option><mat-option value="grid">grid</mat-option><mat-option value="row">row</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'hero') {<mat-form-field appearance="outline"><mat-label>{{ tx('editor.grouping.emphasis', 'Emphasis') }}</mat-label><mat-select formControlName="emphasis"><mat-option value="">{{ tx('editor.grouping.layoutDefault', 'Default') }}</mat-option><mat-option value="high">high</mat-option><mat-option value="medium">medium</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'rail') {<mat-form-field appearance="outline"><mat-label>{{ tx('editor.grouping.side', 'Side') }}</mat-label><mat-select formControlName="side"><mat-option value="left">left</mat-option><mat-option value="right">right</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'tabs') {<mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.grouping.tabsJson', 'Tabs (JSON)') }}</mat-label><textarea matInput rows="4" formControlName="tabsJson"></textarea></mat-form-field>} @else {<mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.grouping.widgetKeysCsv', 'Widget keys (comma-separated)') }}</mat-label><input matInput formControlName="widgetKeysCsv" /></mat-form-field>}</div></div>}</div></mat-tab>
13487
- <mat-tab [label]="tx('editor.tabs.devices', 'Device Layouts')">
13488
- <div class="panel">
13489
- <mat-button-toggle-group [value]="activeDevice()" (change)="activeDevice.set($event.value)" [attr.aria-label]="tx('editor.devices.target', 'Device to configure')">
13490
- @for (device of deviceKeys; track device) {
13491
- <mat-button-toggle [value]="device">{{ deviceLabels[device] }}</mat-button-toggle>
13492
- }
13493
- </mat-button-toggle-group>
13494
- @for (device of deviceKeys; track device) {
13495
- @if (activeDevice() === device) {
13496
- <div class="card">
13497
- <div class="head"><strong>{{ deviceLabels[device] }}</strong><button mat-stroked-button type="button" [attr.data-testid]="'stack-device-' + device" [disabled]="!hasCanvasWidgets" (click)="stackDevice(device)"><mat-icon aria-hidden="true">view_agenda</mat-icon>{{ tx('editor.devices.stack', 'Stack widgets') }}</button></div>
13498
- <p class="hint">{{ tx('editor.devices.stackHelp', 'Use full width and natural height, in reading order. This replaces positions and width limits only for this device; other layouts stay unchanged.') }}</p>
13499
- @if (stackedDevices.has(device)) { <p class="hint" role="status">{{ tx('editor.devices.stacked', 'Single-column layout prepared. Save to apply; Reset undoes the changes.') }}</p> }
13500
- <div class="grid">
13501
- <mat-form-field appearance="outline"><mat-label>{{ tx('editor.devices.columns', 'Columns') }}</mat-label><input matInput type="number" min="1" [formControl]="deviceForms[device].controls.columns" /></mat-form-field>
13502
- <mat-form-field class="page-inset-field" appearance="outline"><mat-label>{{ tx('editor.layout.paddingInline', 'Page side inset') }}</mat-label><input matInput [formControl]="deviceForms[device].controls.paddingInline" placeholder="16px" /></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.devices.gap', 'Gap') }}</mat-label><input matInput [formControl]="deviceForms[device].controls.gap" /></mat-form-field>
13503
- <mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.autoRows', 'Auto rows') }}</mat-label><mat-select [formControl]="deviceForms[device].controls.autoRows"><mat-option value="">{{ tx('editor.devices.orientationDefault', 'Default') }}</mat-option><mat-option value="fixed">{{ tx('editor.layout.autoRowsFixed', 'Fixed height') }}</mat-option><mat-option value="content">{{ tx('editor.layout.autoRowsContent', 'Fit content') }}</mat-option></mat-select></mat-form-field>
13504
- </div>
13505
- <details><summary>{{ tx('editor.devices.advanced', 'Advanced overrides') }}</summary><div class="grid">
13506
- <mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.rowUnit', 'Row unit') }}</mat-label><input matInput [formControl]="deviceForms[device].controls.rowUnit" /></mat-form-field>
13507
- <mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.devices.groupingOverridesJson', 'Grouping overrides (JSON)') }}</mat-label><textarea matInput rows="4" [formControl]="deviceForms[device].controls.groupingOverrides"></textarea></mat-form-field>
13508
- <mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.devices.widgetOverridesJson', 'Widget overrides (JSON)') }}</mat-label><textarea matInput rows="4" [formControl]="deviceForms[device].controls.widgetOverrides"></textarea></mat-form-field>
13509
- </div></details>
13510
- @if (!stackedDevices.has(device)) { <div class="hint">{{ tx('editor.preview.deviceCanvasHint', 'Existing canvas item overrides are preserved.') }}</div> }
13511
- </div>
13512
- }
13513
- }
13514
- </div>
13515
- </mat-tab>
13516
- <mat-tab [label]="tx('editor.tabs.context', 'Context')"><mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.context.json', 'Context (JSON)') }}</mat-label><textarea matInput rows="12" [formControl]="contextControl"></textarea></mat-form-field></mat-tab>
13517
- <mat-tab [label]="tx('editor.tabs.state', 'Page State')"><mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.state.valuesJson', 'Primary values (JSON)') }}</mat-label><textarea matInput rows="14" [formControl]="stateControl"></textarea></mat-form-field></mat-tab>
13518
- <mat-tab [label]="tx('editor.limits.tab', 'Widget limits')">
13519
- <section class="limits-panel" data-testid="canvas-limits-editor">
13520
- <p class="hint" id="canvas-limits-help">{{ tx('editor.limits.help', 'Limits use grid columns and rows, not pixels. Leave blank for defaults, or to inherit the base limits on a device.') }}</p>
13521
- @if (canvasLimits.targets.length) {
13522
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13523
- <mat-label>{{ tx('editor.limits.target', 'Widget and layout') }}</mat-label>
13524
- <mat-select [formControl]="limitsTargetControl" data-testid="canvas-limits-target">
13525
- @for (target of canvasLimits.targets; track target.scope + ':' + target.key; let index = $index) {
13526
- <mat-option [value]="index">{{ limitsTargetLabel(index) }}</mat-option>
13527
- }
13528
- </mat-select>
13529
- </mat-form-field>
13530
- <p class="hint">{{ limitsGeometryLabel() }}</p>
13531
- @if (canvasLimits.form.controls[limitsTargetControl.value]; as limitsForm) {
13532
- <div class="limits-fields" aria-describedby="canvas-limits-help">
13533
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13534
- <mat-label>{{ tx('editor.limits.minColSpan', 'Minimum width (columns)') }}</mat-label>
13535
- <input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.minColSpan" data-testid="canvas-limit-minColSpan">
13536
- <mat-error>{{ limitFieldError(limitsForm.controls.minColSpan) }}</mat-error>
13537
- </mat-form-field>
13538
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13539
- <mat-label>{{ tx('editor.limits.maxColSpan', 'Maximum width (columns)') }}</mat-label>
13540
- <input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.maxColSpan" data-testid="canvas-limit-maxColSpan">
13541
- <mat-error>{{ limitFieldError(limitsForm.controls.maxColSpan) }}</mat-error>
13542
- </mat-form-field>
13543
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13544
- <mat-label>{{ tx('editor.limits.minRowSpan', 'Minimum height (rows)') }}</mat-label>
13545
- <input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.minRowSpan" data-testid="canvas-limit-minRowSpan">
13546
- <mat-error>{{ limitFieldError(limitsForm.controls.minRowSpan) }}</mat-error>
13547
- </mat-form-field>
13548
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13549
- <mat-label>{{ tx('editor.limits.maxRowSpan', 'Maximum height (rows)') }}</mat-label>
13550
- <input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.maxRowSpan" data-testid="canvas-limit-maxRowSpan">
13551
- <mat-error>{{ limitFieldError(limitsForm.controls.maxRowSpan) }}</mat-error>
13552
- </mat-form-field>
13553
- </div>
13554
- @if (limitsUseContentHeight()) {
13555
- <p class="limits-notice"><mat-icon aria-hidden="true">height</mat-icon><span>{{ tx('editor.limits.autoHeight', 'Choose natural height or an adjustable height with internal scrolling.') }}</span></p>
13556
- <mat-button-toggle-group class="height-modes" [formControl]="limitsForm.controls.heightMode" [attr.aria-label]="tx('editor.height.mode', 'Widget height')" data-testid="canvas-height-mode">
13557
- <mat-button-toggle value="inherit">{{ tx('editor.height.inherit', 'Default') }}</mat-button-toggle>
13558
- <mat-button-toggle value="auto">{{ tx('editor.height.auto', 'Automatic') }}</mat-button-toggle>
13559
- <mat-button-toggle value="fixed">{{ tx('editor.height.fixed', 'Adjustable') }}</mat-button-toggle>
13560
- </mat-button-toggle-group>
13561
- @if (limitsForm.controls.heightMode.value === 'fixed') {
13562
- <div class="limits-fields">
13563
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13564
- <mat-label>{{ tx('editor.height.value', 'Height (px)') }}</mat-label>
13565
- <input matInput type="number" min="160" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.heightPx" data-testid="canvas-height-px">
13566
- <mat-error>{{ limitFieldError(limitsForm.controls.heightPx) }}</mat-error>
13567
- </mat-form-field>
13568
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13569
- <mat-label>{{ tx('editor.height.top', 'Top inset (px)') }}</mat-label>
13570
- <input matInput type="number" min="0" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.offsetTopPx" data-testid="canvas-height-top">
13571
- <mat-error>{{ limitFieldError(limitsForm.controls.offsetTopPx) }}</mat-error>
13572
- </mat-form-field>
13573
- </div>
13574
- }
13575
- <div class="limits-fields">
13576
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13577
- <mat-label>{{ tx('editor.height.min', 'Minimum height (px)') }}</mat-label>
13578
- <input matInput type="number" min="160" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.minHeightPx" data-testid="canvas-limit-minHeightPx">
13579
- <mat-error>{{ limitFieldError(limitsForm.controls.minHeightPx) }}</mat-error>
13580
- </mat-form-field>
13581
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13582
- <mat-label>{{ tx('editor.height.max', 'Maximum height (px)') }}</mat-label>
13583
- <input matInput type="number" min="160" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.maxHeightPx" data-testid="canvas-limit-maxHeightPx">
13584
- <mat-error>{{ limitFieldError(limitsForm.controls.maxHeightPx) }}</mat-error>
13585
- </mat-form-field>
13586
- </div>
13587
- }
13588
- }
13589
- @if (limitsSummaryError) { <p class="limits-error" role="alert">{{ limitsSummaryError }}</p> }
13590
- } @else {
13591
- <p>{{ tx('editor.limits.empty', 'Add widgets to the canvas to configure their limits.') }}</p>
13592
- }
13593
- </section>
13594
- </mat-tab>
13595
- </mat-tab-group>
13596
- </div>
13597
- `, isInline: true, styles: [".page-inset-field{grid-column:1 / -1;min-width:0}:host{display:block;min-width:0;max-width:100%}.height-modes{width:fit-content;max-width:100%;flex-wrap:wrap;margin-bottom:16px}.editor,.panel{display:grid;gap:16px;min-width:0;max-width:100%;color:var(--md-sys-color-on-surface, currentColor)}mat-tab-group,mat-form-field{min-width:0;max-width:100%}.head,.summary{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.summary{color:var(--md-sys-color-on-surface-variant, currentColor)}.selection-identity{position:sticky;top:0;z-index:var(--praxis-layer-sticky-local, 100);display:flex;align-items:flex-start;gap:8px;padding-block:8px;background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface));overflow-wrap:anywhere}.selection-identity mat-icon{flex-shrink:0}:host ::ng-deep .editor-tabs>.mat-mdc-tab-header .mdc-tab{min-width:0;padding-inline:12px}:host ::ng-deep .editor-tabs>.mat-mdc-tab-header .mdc-tab__text-label{max-width:128px;white-space:normal;overflow-wrap:anywhere;text-align:center;letter-spacing:normal;line-height:1.25}.grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr))}@media(max-width:600px){.grid{grid-template-columns:minmax(0,1fr)}.head,.summary,.preset-preview__head,.preset-preview__recommendation{align-items:stretch}.head>button,.preset-preview__recommendation button{width:100%}}.card{display:grid;gap:12px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 18%, transparent));border-radius:8px;background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface-container, transparent));color:var(--md-sys-color-on-surface, currentColor)}.limits-panel{display:grid;gap:8px;min-width:0}.limits-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:4px 16px}.limits-fields mat-form-field{min-width:0}.limits-notice{display:flex;gap:8px;align-items:flex-start;color:var(--md-sys-color-on-surface-variant, currentColor)}.limits-notice mat-icon{flex-shrink:0}.limits-error{color:var(--md-sys-color-error, #b3261e)}.wide{grid-column:1 / -1}.hint{color:var(--md-sys-color-on-surface-variant, currentColor);font-size:12px}.preset-preview{display:grid;gap:12px;margin-top:12px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 18%, transparent));border-radius:8px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low, transparent) 88%,var(--md-sys-color-primary-container, transparent) 12%)}.preset-preview--theme{background:var(--md-sys-color-surface-container-low, transparent)}.preset-preview__head,.preset-preview__recommendation{display:flex;justify-content:space-between;align-items:center;gap:16px}.preset-preview__head>div,.preset-preview__recommendation>div{display:grid;gap:4px;min-width:0}.preset-preview p{margin:0;color:var(--md-sys-color-on-surface-variant, currentColor);line-height:1.45}.preset-preview__chips{display:flex;flex-wrap:wrap;gap:8px}.preset-preview__chips span{padding:5px 9px;border-radius:999px;background:var(--md-sys-color-surface-container, color-mix(in srgb, currentColor 8%, transparent));color:var(--md-sys-color-on-surface-variant, currentColor);font-size:12px;line-height:1.3}.preset-preview__recommendation{padding-top:12px;border-top:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 14%, transparent))}.preset-preview__recommendation span:last-child{color:var(--md-sys-color-on-surface-variant, currentColor);font-size:13px;line-height:1.35}.eyebrow{color:var(--md-sys-color-primary, currentColor);font-size:11px;font-weight:700;letter-spacing:0;text-transform:uppercase}:host ::ng-deep .mat-mdc-tab-body-content{padding-top:12px;box-sizing:border-box}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i3$1.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i3$1.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i8$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i8$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14133
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DynamicPageConfigEditorComponent, isStandalone: true, selector: "praxis-dynamic-page-config-editor", inputs: { page: "page", layout: "layout", presets: "presets", pagePreset: "pagePreset" }, outputs: { pageChange: "pageChange" }, host: { listeners: { "window:resize": "refreshRenderedValidation()" } }, providers: [providePraxisPageBuilderI18n()], viewQueries: [{ propertyName: "arrangementFeedback", first: true, predicate: ["arrangementFeedback"], descendants: true }], ngImport: i0, template: "<div class=\"editor\">\n @if (!isHostedInSettingsPanel) { <div class=\"head\">\n <div>{{ tx('editor.title', 'Page settings') }}</div>\n <button mat-stroked-button type=\"button\" (click)=\"reset()\">{{ tx('editor.reload', 'Reload') }}</button>\n </div> }\n\n @if (nonLimitError) { <p class=\"limits-error\" role=\"alert\">{{ nonLimitError }}</p> }\n <mat-tab-group class=\"editor-tabs studio-tabs\" [mat-stretch-tabs]=\"false\" [selectedIndex]=\"workspaceTab()\" (selectedIndexChange)=\"workspaceTab.set($event)\">\n <mat-tab [label]=\"tx('studio.visualTab', 'Visual templates')\"><ng-template matTabContent>\n <section class=\"studio-panel\" data-testid=\"studio-visual\">\n <header class=\"studio-heading\"><span class=\"eyebrow\">{{ tx('studio.visualEyebrow', 'Appearance') }}</span><h2>{{ tx('studio.visualTitle', 'Give the page a visual language') }}</h2><p>{{ tx('studio.visualHelp', 'Choose surfaces, density and chart styling together. Individual widget overrides still take precedence.') }}</p></header>\n <button mat-stroked-button type=\"button\" [attr.aria-pressed]=\"!themePresetControl.value\" (click)=\"themePresetControl.setValue('')\">{{ tx('editor.layout.layoutDefault', 'Layout default') }}</button>\n <p class=\"selection-line\">{{ tx('presets.effectiveTheme', 'Effective page theme') }}: <strong>{{ selectedThemePresetEntry?.label || themePresetControl.value || recommendedThemePresetEntry?.label || tx('presets.hostTheme', 'Host theme') }}</strong></p>\n <div class=\"studio-gallery\">\n @for (theme of themePresetEntries; track theme.id) {\n <button type=\"button\" class=\"studio-choice\" [class.selected]=\"themePresetControl.value === theme.id\" [attr.aria-pressed]=\"themePresetControl.value === theme.id\" [attr.data-theme-preset]=\"theme.id\" (click)=\"themePresetControl.setValue(theme.id)\">\n <span class=\"theme-sample\" aria-hidden=\"true\" [style.background]=\"themeTokens(theme.id)['--pdx-page-bg']\" [style.--sample-accent]=\"themeTokens(theme.id)['--pdx-page-accent']\" [style.--sample-surface]=\"themeTokens(theme.id)['--pdx-page-surface']\" [style.--sample-radius]=\"themeTokens(theme.id)['--pdx-page-card-radius']\" [style.--sample-shadow]=\"themeTokens(theme.id)['--pdx-page-card-shadow']\" [style.gap]=\"themeTokens(theme.id)['--pdx-page-gap']\">\n <span class=\"sample-metric\"><b>128</b><i></i></span><span class=\"sample-metric\"><b>86%</b><i></i></span><span class=\"sample-chart\"><i></i><i></i><i></i><i></i><i></i></span>\n </span>\n <span class=\"choice-title\">{{ theme.label }}@if (themePresetControl.value === theme.id) {<mat-icon aria-hidden=\"true\">check_circle</mat-icon>}</span>\n <span class=\"choice-description\">{{ tx('studio.theme.' + theme.id, theme.label) }}</span>\n <span class=\"choice-meta\">{{ tx('studio.density.' + theme.density, theme.density) }} \u00B7 {{ shellName(theme.shellPreset) }}</span>\n </button>\n }\n </div>\n <p class=\"hint\">{{ tx('studio.samplesHelp', 'Illustrative samples using the preset tokens. Apply to see the result with your content.') }}</p>\n <details class=\"shell-options\"><summary>{{ tx('studio.shellTitle', 'Customize widget surfaces') }} \u00B7 {{ shellPresetEntries.length }}</summary>\n <p>{{ tx('studio.shellHelp', 'Override the page theme\u2019s default widget surface. Per-widget appearance settings keep their priority.') }}</p>\n <button mat-stroked-button type=\"button\" [attr.aria-pressed]=\"!shellPresetControl.value\" (click)=\"shellPresetControl.setValue('')\">{{ tx('editor.layout.inheritShell', 'Inherit appearance') }}</button>\n <div class=\"studio-gallery shell-gallery\">@for (shell of shellPresetEntries; track shell.id) {\n <button type=\"button\" class=\"studio-choice\" [class.selected]=\"shellPresetControl.value === shell.id\" [attr.aria-pressed]=\"shellPresetControl.value === shell.id\" (click)=\"shellPresetControl.setValue(shell.id)\">\n <span class=\"shell-sample\" aria-hidden=\"true\" [style.background]=\"shell.appearance.card?.background\" [style.border-color]=\"shell.appearance.card?.borderColor\" [style.border-radius]=\"shell.appearance.card?.borderRadius\" [style.box-shadow]=\"shell.appearance.card?.shadow\"><span [style.background]=\"shell.appearance.header?.background\" [style.border-color]=\"shell.appearance.header?.borderColor\"><mat-icon>analytics</mat-icon><i></i></span><span [style.background]=\"shell.appearance.body?.background\"><i></i><i></i></span></span>\n <span class=\"choice-title\">{{ shellPresetLabel(shell) }}@if (shellPresetControl.value === shell.id) {<mat-icon aria-hidden=\"true\">check_circle</mat-icon>}</span><span class=\"choice-description\">{{ shellDescription(shell) }}</span>\n </button>\n }</div>\n </details>\n </section>\n </ng-template></mat-tab>\n <mat-tab [label]=\"tx('studio.layoutTab', 'Layout templates')\"><ng-template matTabContent>\n <section class=\"studio-panel\" data-testid=\"studio-layout\">\n <header class=\"studio-heading\"><span class=\"eyebrow\">{{ tx('studio.layoutEyebrow', 'Composition model') }}</span><h2>{{ tx('studio.layoutTitle', 'Choose the structure for your use case') }}</h2><p>{{ tx('studio.layoutHelp', 'Compare the roles and default theme of each model. To redistribute the widgets already on this canvas, open Arrange.') }}</p></header>\n <div class=\"studio-actions\"><button mat-stroked-button type=\"button\" (click)=\"layoutPresetControl.setValue('')\">{{ tx('studio.inheritLayout', 'Use inherited model') }}</button><button mat-button type=\"button\" (click)=\"workspaceTab.set(2)\">{{ tx('studio.goArrange', 'Arrange existing widgets') }}<mat-icon iconPositionEnd>arrow_forward</mat-icon></button></div>\n <praxis-page-preset-comparison [expanded]=\"true\" [layoutId]=\"layoutPresetControl.value\" [layoutPresetOptions]=\"layoutPresetOptions\" [themeId]=\"themePresetControl.value\" [canvas]=\"true\" (choose)=\"layoutPresetControl.setValue($event)\" />\n </section>\n </ng-template></mat-tab>\n <mat-tab [label]=\"tx('studio.arrangeTab', 'Arrange')\"><ng-template matTabContent>\n <section class=\"studio-panel\" data-testid=\"canvas-arrangement\">\n <header class=\"studio-heading\"><span class=\"eyebrow\">{{ tx('studio.arrangeEyebrow', 'Existing widgets') }} \u00B7 {{ canvasColumnsControl.value }} {{ tx('editor.layout.cols', 'Columns') }}</span><h2>{{ tx('studio.arrangeTitle', 'Find the right balance of space') }}</h2><p>{{ tx('studio.arrangeHelp', 'Choose a distribution, compare the real positions, then use it in the draft. Widgets keep their reading order and content.') }}</p></header>\n <div #arrangementFeedback tabindex=\"-1\" class=\"arrangement-feedback\">\n @if (arrangementPreview(); as preview) {\n <section class=\"arrangement-review\" aria-live=\"polite\" data-testid=\"arrangement-preview\">\n <div class=\"review-heading\"><h3>{{ tx('studio.reviewTitle', 'Compare before using') }}</h3><span>{{ preview.keys.length }} {{ tx('studio.changedWidgets', 'widgets change') }}</span></div>\n <div class=\"canvas-comparison\">\n @for (side of ['before', 'after']; track side) {\n <figure><figcaption>{{ side === 'before' ? tx('studio.before', 'Before') : tx('studio.after', 'After') }}</figcaption>\n <div class=\"canvas-map\" [style.grid-template-columns]=\"'repeat(' + preview.canvas.columns + ', minmax(0, 1fr))'\" [style.grid-template-rows]=\"'repeat(' + previewRowCount(preview.before, preview.canvas) + ', minmax(0, 1fr))'\" role=\"img\" [attr.aria-label]=\"side === 'before' ? tx('studio.before', 'Before') : tx('studio.after', 'After')\">\n @for (item of previewItems(side === 'before' ? preview.before : preview.canvas); track item.key) {\n <span [style.grid-column]=\"item.col + ' / span ' + item.colSpan\" [style.grid-row]=\"item.row + ' / span ' + item.rowSpan\" [class.changed]=\"preview.keys.includes(item.key)\" [matTooltip]=\"item.label\">{{ item.number }}</span>\n }\n </div>\n </figure>\n }\n </div>\n <p class=\"hint\">{{ tx('studio.mapHelp', 'Numbered map of your widgets. Positions and spans are to scale; content height is evaluated on the page.') }}</p>\n <details><summary>{{ tx('studio.widgetDetails', 'Widget positions and sizes') }}</summary><ol class=\"widget-changes\">@for (item of previewItems(preview.canvas); track item.key) {<li><strong>{{ item.label }}</strong><span>{{ tx('studio.before', 'Before') }}: {{ tx('editor.arrange.position', 'Column / row') }} {{ preview.before.items[item.key].col }} / {{ preview.before.items[item.key].row }} \u00B7 {{ tx('studio.gridSize', 'Size in columns \u00D7 rows') }} {{ preview.before.items[item.key].colSpan }} \u00D7 {{ preview.before.items[item.key].rowSpan }}</span><span>{{ tx('studio.after', 'After') }}: {{ tx('editor.arrange.position', 'Column / row') }} {{ item.col }} / {{ item.row }} \u00B7 {{ tx('studio.gridSize', 'Size in columns \u00D7 rows') }} {{ item.colSpan }} \u00D7 {{ item.rowSpan }}</span></li>}</ol></details>\n <button mat-flat-button type=\"button\" data-testid=\"arrangement-use\" (click)=\"useArrangement()\">{{ tx('editor.arrange.use', 'Use arrangement in draft') }}<mat-icon iconPositionEnd>arrow_forward</mat-icon></button>\n </section>\n }\n @if (arrangementMessage()) {<p class=\"studio-status\" role=\"status\">{{ arrangementMessage() }}</p>}\n @if (arrangementUndoAvailable()) {<button mat-stroked-button type=\"button\" data-testid=\"arrangement-undo\" (click)=\"undoArrangement()\"><mat-icon>undo</mat-icon>{{ tx('editor.arrange.undo', 'Undo arrangement') }}</button>}\n </div>\n <div class=\"studio-gallery arrangement-gallery\">\n @for (choice of arrangementChoices; track choice.id) {\n <button type=\"button\" class=\"studio-choice\" [class.selected]=\"arrangementSelection() === choice.id\" [attr.aria-pressed]=\"arrangementSelection() === choice.id\" [disabled]=\"!hasCanvasWidgets || !arrangementFits(choice.id)\" [attr.data-testid]=\"'arrange-' + choice.id\" (click)=\"prepareArrangement(choice.id)\">\n <span class=\"distribution-sample\" aria-hidden=\"true\" [style.grid-template-columns]=\"(arrangementFits(choice.id) ? arrangementWidths(choice.id) : choice.weights).join('fr ') + 'fr'\">@for (weight of choice.weights; track $index) {<i>{{ arrangementWidths(choice.id)[$index] }}</i>}@for (weight of choice.weights; track $index) {<i></i>}</span>\n <span class=\"choice-title\">{{ tx('editor.arrange.' + choice.id, choice.id) }}@if (arrangementSelection() === choice.id) {<mat-icon aria-hidden=\"true\">check_circle</mat-icon>}</span>\n <span class=\"choice-description\">{{ tx('studio.arrange.' + choice.id, choice.id) }}</span>\n <span class=\"choice-meta\">{{ arrangementFits(choice.id) ? arrangementWidths(choice.id).join(' + ') : tx('studio.moreColumns', 'Needs more grid columns') }}</span>\n </button>\n }\n </div>\n @if (!hasCanvasWidgets) {<p role=\"status\">{{ tx('editor.limits.empty', 'Add widgets to the canvas to configure their limits.') }}</p>}\n <details class=\"arrangement-rules\"><summary>{{ tx('studio.rules', 'Scope, devices and locked widgets') }}</summary><p>{{ tx('editor.arrange.help', 'Base canvas, in current reading order. Inheriting devices follow the base; explicit device positions are preserved. Locked positions stay in place.') }}</p><button mat-button type=\"button\" (click)=\"onSelectedTabIndexChange(5)\">{{ tx('studio.goLimits', 'Review widget limits') }}</button></details>\n </section>\n </ng-template></mat-tab>\n <mat-tab [label]=\"tx('studio.settingsTab', 'Adjustments')\">\n <div class=\"studio-panel\"><mat-tab-group class=\"editor-tabs\" [mat-stretch-tabs]=\"false\" [selectedIndex]=\"selectedTabIndex\" (selectedIndexChange)=\"selectDetailTab($event)\">\n <mat-tab [label]=\"tx('editor.tabs.layout', 'Layout')\"><div class=\"grid\"><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.cols', 'Columns') }}</mat-label><input matInput type=\"number\" min=\"1\" [formControl]=\"canvasColumnsControl\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.rowUnit', 'Row unit') }}</mat-label><input matInput [formControl]=\"canvasRowUnitControl\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.gap', 'Gap') }}</mat-label><input matInput [formControl]=\"canvasGapControl\" /></mat-form-field><mat-form-field class=\"page-inset-field\" appearance=\"outline\"><mat-label>{{ tx('editor.layout.paddingInline', 'Page side inset') }}</mat-label><input matInput [formControl]=\"paddingInlineControl\" placeholder=\"24px\" /><button praxisIconButton=\"help_outline\" size=\"dense\" matSuffix type=\"button\" [matTooltip]=\"tx('editor.hints.paddingInline', 'Shared space beside all page widgets. Use a CSS length or theme token; 0px removes it. Leave empty to inherit the host theme.')\" [attr.aria-label]=\"tx('editor.layout.paddingInline', 'Page side inset')\"></button></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.autoRows', 'Auto rows') }}</mat-label><mat-select [formControl]=\"canvasAutoRowsControl\"><mat-option value=\"fixed\">{{ tx('editor.layout.autoRowsFixed', 'Fixed height') }}</mat-option><mat-option value=\"content\">{{ tx('editor.layout.autoRowsContent', 'Fit content') }}</mat-option></mat-select></mat-form-field></div></mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.grouping', 'Grouping')\"><div class=\"panel\">\n <p class=\"hint\" data-testid=\"canvas-grouping-notice\">{{ tx('editor.grouping.canvasNotice', 'Canvas positions control this page. Groups below are preserved for flow layouts and do not arrange widgets in the canvas. Use the widget move and resize controls to place them side by side.') }}</p><div class=\"head\"><span>{{ tx('editor.grouping.title', 'Grouping') }}</span><button mat-stroked-button type=\"button\" (click)=\"addGroupingNode()\"><mat-icon>add</mat-icon>{{ tx('editor.grouping.add', 'Add group') }}</button></div>@for (group of groupingNodes.controls; track $index) {<div class=\"card\" [formGroup]=\"group\"><div class=\"head\"><span>{{ groupLabel($index) }}</span><button praxisIconButton=\"delete\" size=\"compact\" type=\"button\" [attr.aria-label]=\"tx('editor.grouping.removeAria', 'Remove group')\" (click)=\"removeGroupingNode($index)\"></button></div><div class=\"grid\"><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.kind', 'Kind') }}</mat-label><mat-select formControlName=\"kind\"><mat-option value=\"section\">section</mat-option><mat-option value=\"tabs\">tabs</mat-option><mat-option value=\"hero\">hero</mat-option><mat-option value=\"rail\">rail</mat-option></mat-select></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.id', 'Id') }}</mat-label><input matInput formControlName=\"id\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.label', 'Label') }}</mat-label><input matInput formControlName=\"label\" /></mat-form-field>@if (group.controls.kind.value === 'section') {<mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.layout', 'Layout') }}</mat-label><mat-select formControlName=\"layout\"><mat-option value=\"\">{{ tx('editor.grouping.layoutDefault', 'Default') }}</mat-option><mat-option value=\"stack\">stack</mat-option><mat-option value=\"grid\">grid</mat-option><mat-option value=\"row\">row</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'hero') {<mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.emphasis', 'Emphasis') }}</mat-label><mat-select formControlName=\"emphasis\"><mat-option value=\"\">{{ tx('editor.grouping.layoutDefault', 'Default') }}</mat-option><mat-option value=\"high\">high</mat-option><mat-option value=\"medium\">medium</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'rail') {<mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.side', 'Side') }}</mat-label><mat-select formControlName=\"side\"><mat-option value=\"left\">left</mat-option><mat-option value=\"right\">right</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'tabs') {<mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.grouping.tabsJson', 'Tabs (JSON)') }}</mat-label><textarea matInput rows=\"4\" formControlName=\"tabsJson\"></textarea></mat-form-field>} @else {<mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.grouping.widgetKeysCsv', 'Widget keys (comma-separated)') }}</mat-label><input matInput formControlName=\"widgetKeysCsv\" /></mat-form-field>}</div></div>}</div></mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.devices', 'Device Layouts')\">\n <div class=\"panel\">\n <mat-button-toggle-group [value]=\"activeDevice()\" (change)=\"activeDevice.set($event.value)\" [attr.aria-label]=\"tx('editor.devices.target', 'Device to configure')\">\n @for (device of deviceKeys; track device) {\n <mat-button-toggle [value]=\"device\">{{ deviceLabels[device] }}</mat-button-toggle>\n }\n </mat-button-toggle-group>\n @for (device of deviceKeys; track device) {\n @if (activeDevice() === device) {\n <div class=\"card\">\n <div class=\"head\"><strong>{{ deviceLabels[device] }}</strong><button mat-stroked-button type=\"button\" [attr.data-testid]=\"'stack-device-' + device\" [disabled]=\"!hasCanvasWidgets\" (click)=\"stackDevice(device)\"><mat-icon aria-hidden=\"true\">view_agenda</mat-icon>{{ tx('editor.devices.stack', 'Stack widgets') }}</button></div>\n <p class=\"hint\">{{ tx('editor.devices.stackHelp', 'Use full width and natural height, in reading order. This replaces positions and width limits only for this device; other layouts stay unchanged.') }}</p>\n @if (stackedDevices.has(device)) { <p class=\"hint\" role=\"status\">{{ tx('editor.devices.stacked', 'Single-column layout prepared. Save to apply; Reset undoes the changes.') }}</p> }\n <div class=\"grid\">\n <mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.devices.columns', 'Columns') }}</mat-label><input matInput type=\"number\" min=\"1\" [formControl]=\"deviceForms[device].controls.columns\" /></mat-form-field>\n <mat-form-field class=\"page-inset-field\" appearance=\"outline\"><mat-label>{{ tx('editor.layout.paddingInline', 'Page side inset') }}</mat-label><input matInput [formControl]=\"deviceForms[device].controls.paddingInline\" placeholder=\"16px\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.devices.gap', 'Gap') }}</mat-label><input matInput [formControl]=\"deviceForms[device].controls.gap\" /></mat-form-field>\n <mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.autoRows', 'Auto rows') }}</mat-label><mat-select [formControl]=\"deviceForms[device].controls.autoRows\"><mat-option value=\"\">{{ tx('editor.devices.orientationDefault', 'Default') }}</mat-option><mat-option value=\"fixed\">{{ tx('editor.layout.autoRowsFixed', 'Fixed height') }}</mat-option><mat-option value=\"content\">{{ tx('editor.layout.autoRowsContent', 'Fit content') }}</mat-option></mat-select></mat-form-field>\n </div>\n <details><summary>{{ tx('editor.devices.advanced', 'Advanced overrides') }}</summary><div class=\"grid\">\n <mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.rowUnit', 'Row unit') }}</mat-label><input matInput [formControl]=\"deviceForms[device].controls.rowUnit\" /></mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.devices.groupingOverridesJson', 'Grouping overrides (JSON)') }}</mat-label><textarea matInput rows=\"4\" [formControl]=\"deviceForms[device].controls.groupingOverrides\"></textarea></mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.devices.widgetOverridesJson', 'Widget overrides (JSON)') }}</mat-label><textarea matInput rows=\"4\" [formControl]=\"deviceForms[device].controls.widgetOverrides\"></textarea></mat-form-field>\n </div></details>\n @if (!stackedDevices.has(device)) { <div class=\"hint\">{{ tx('editor.preview.deviceCanvasHint', 'Existing canvas item overrides are preserved.') }}</div> }\n </div>\n }\n }\n </div>\n </mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.context', 'Context')\"><mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.context.json', 'Context (JSON)') }}</mat-label><textarea matInput rows=\"12\" [formControl]=\"contextControl\"></textarea></mat-form-field></mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.state', 'Page State')\"><mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.state.valuesJson', 'Primary values (JSON)') }}</mat-label><textarea matInput rows=\"14\" [formControl]=\"stateControl\"></textarea></mat-form-field></mat-tab>\n <mat-tab [label]=\"tx('editor.limits.tab', 'Widget limits')\">\n <section class=\"limits-panel\" data-testid=\"canvas-limits-editor\">\n @if (workspaceTab() === 3 && activeTab() === 'limits' && canvasLimits.targets.length) {\n <div class=\"selection-identity\" data-testid=\"canvas-limits-identity\">\n <mat-icon aria-hidden=\"true\">dashboard_customize</mat-icon>\n <strong>{{ limitsTargetLabel(limitsTargetControl.value) }}</strong>\n </div>\n }\n\n <p class=\"hint\" id=\"canvas-limits-help\">{{ tx('editor.limits.help', 'Limits use grid columns and rows, not pixels. Leave blank for defaults, or to inherit the base limits on a device.') }}</p>\n @if (canvasLimits.targets.length) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.target', 'Widget and layout') }}</mat-label>\n <mat-select [formControl]=\"limitsTargetControl\" data-testid=\"canvas-limits-target\">\n @for (target of canvasLimits.targets; track target.scope + ':' + target.key; let index = $index) {\n <mat-option [value]=\"index\">{{ limitsTargetLabel(index) }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <p class=\"hint\">{{ limitsGeometryLabel() }}</p>\n @if (canvasLimits.form.controls[limitsTargetControl.value]; as limitsForm) {\n <div class=\"limits-fields\" aria-describedby=\"canvas-limits-help\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.minColSpan', 'Minimum width (columns)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.minColSpan\" data-testid=\"canvas-limit-minColSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.minColSpan) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.maxColSpan', 'Maximum width (columns)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.maxColSpan\" data-testid=\"canvas-limit-maxColSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.maxColSpan) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.minRowSpan', 'Minimum height (rows)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.minRowSpan\" data-testid=\"canvas-limit-minRowSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.minRowSpan) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.maxRowSpan', 'Maximum height (rows)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.maxRowSpan\" data-testid=\"canvas-limit-maxRowSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.maxRowSpan) }}</mat-error>\n </mat-form-field>\n </div>\n @if (limitsUseContentHeight()) {\n <p class=\"limits-notice\"><mat-icon aria-hidden=\"true\">height</mat-icon><span>{{ tx('editor.limits.autoHeight', 'Choose natural height or an adjustable height with internal scrolling.') }}</span></p>\n <mat-button-toggle-group class=\"height-modes\" [formControl]=\"limitsForm.controls.heightMode\" [attr.aria-label]=\"tx('editor.height.mode', 'Widget height')\" data-testid=\"canvas-height-mode\">\n <mat-button-toggle value=\"inherit\">{{ tx('editor.height.inherit', 'Default') }}</mat-button-toggle>\n <mat-button-toggle value=\"auto\">{{ tx('editor.height.auto', 'Automatic') }}</mat-button-toggle>\n <mat-button-toggle value=\"fixed\">{{ tx('editor.height.fixed', 'Adjustable') }}</mat-button-toggle>\n </mat-button-toggle-group>\n @if (limitsForm.controls.heightMode.value === 'fixed') {\n <div class=\"limits-fields\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.value', 'Height (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"160\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.heightPx\" data-testid=\"canvas-height-px\">\n <mat-error>{{ limitFieldError(limitsForm.controls.heightPx) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.top', 'Top inset (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"0\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.offsetTopPx\" data-testid=\"canvas-height-top\">\n <mat-error>{{ limitFieldError(limitsForm.controls.offsetTopPx) }}</mat-error>\n </mat-form-field>\n </div>\n }\n <div class=\"limits-fields\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.min', 'Minimum height (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"160\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.minHeightPx\" data-testid=\"canvas-limit-minHeightPx\">\n <mat-error>{{ limitFieldError(limitsForm.controls.minHeightPx) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.max', 'Maximum height (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"160\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.maxHeightPx\" data-testid=\"canvas-limit-maxHeightPx\">\n <mat-error>{{ limitFieldError(limitsForm.controls.maxHeightPx) }}</mat-error>\n </mat-form-field>\n </div>\n }\n }\n @if (limitsSummaryError) { <p class=\"limits-error\" role=\"alert\">{{ limitsSummaryError }}</p> }\n } @else {\n <p>{{ tx('editor.limits.empty', 'Add widgets to the canvas to configure their limits.') }}</p>\n }\n </section>\n </mat-tab>\n </mat-tab-group></div>\n </mat-tab>\n </mat-tab-group>\n <details class=\"summary-details\">\n <summary>{{ tx('editor.summaryAria', 'Configuration summary') }}</summary>\n <div class=\"summary\">\n @if (!isHostedInSettingsPanel) {\n <span>{{ validationStateLabel }}</span>\n <span>{{ dirtyStateLabel }}</span>\n }\n <span>{{ selectedLayoutPresetLabel }}</span>\n <span>{{ selectedThemePresetLabel }}</span>\n <span>{{ canvasSummaryLabel }}</span>\n </div>\n </details>\n </div>\n", styles: [".page-inset-field{grid-column:1/-1;min-width:0}:host{display:block;min-width:0;max-width:100%}.height-modes{width:fit-content;max-width:100%;flex-wrap:wrap;margin-bottom:16px}.editor,.panel{display:grid;gap:16px;min-width:0;max-width:100%;color:var(--md-sys-color-on-surface, currentColor)}mat-tab-group,mat-form-field{min-width:0;max-width:100%}.head,.summary{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.summary{color:var(--md-sys-color-on-surface-variant, currentColor)}.selection-identity{display:flex;align-items:flex-start;gap:8px;padding-block:8px;background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface));overflow-wrap:anywhere}.selection-identity mat-icon{flex-shrink:0}:host ::ng-deep .editor-tabs>.mat-mdc-tab-header .mdc-tab{min-width:0;padding-inline:12px}:host ::ng-deep .editor-tabs>.mat-mdc-tab-header .mdc-tab__text-label{max-width:128px;white-space:normal;overflow-wrap:anywhere;text-align:center;letter-spacing:normal;line-height:1.25}.grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr))}@media(max-width:600px){.grid{grid-template-columns:minmax(0,1fr)}.head,.summary{align-items:stretch}.head>button{width:100%}}.card{display:grid;gap:12px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 18%, transparent));border-radius:8px;background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface-container, transparent));color:var(--md-sys-color-on-surface, currentColor)}.limits-panel{display:grid;gap:8px;min-width:0}.limits-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:4px 16px}.limits-fields mat-form-field{min-width:0}.limits-notice{display:flex;gap:8px;align-items:flex-start;color:var(--md-sys-color-on-surface-variant, currentColor)}.limits-notice mat-icon{flex-shrink:0}.limits-error{color:var(--md-sys-color-error, #b3261e)}.wide{grid-column:1/-1}.hint{color:var(--md-sys-color-on-surface-variant, currentColor);font-size:12px}.eyebrow{color:var(--md-sys-color-primary, currentColor);font-size:11px;font-weight:700;letter-spacing:0;text-transform:uppercase}:host ::ng-deep .mat-mdc-tab-body-content{padding-top:12px;box-sizing:border-box}:host{container-type:inline-size}.studio-panel{display:grid;gap:var(--p-space-4, 16px);padding-block:var(--p-space-5, 20px);min-width:0}.studio-heading h2{font-size:var(--p-font-size-lg, 20px);line-height:1.25;margin:var(--p-space-2, 8px) 0;font-weight:600}.studio-heading p{margin:0;color:var(--md-sys-color-on-surface-variant);line-height:1.5}.studio-panel h3{font-size:var(--p-font-size-md, 16px);margin:0}.studio-panel>button{justify-self:start}.studio-gallery{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-3, 12px)}.studio-choice{display:flex;flex-direction:column;gap:var(--p-space-2, 8px);min-width:0;padding:var(--p-space-3, 12px);text-align:start;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--p-radius-lg, 12px);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);font:inherit;cursor:pointer;transition:border-color var(--md-sys-motion-duration-short2, .1s),background-color var(--md-sys-motion-duration-short2, .1s)}.studio-choice:hover:not(:disabled){border-color:var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low)}.studio-choice.selected{border-color:var(--md-sys-color-primary);box-shadow:inset 0 0 0 1px var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low)}.studio-choice:focus-visible,.studio-panel summary:focus-visible{outline:3px solid var(--md-sys-color-primary);outline-offset:3px}.studio-choice:disabled{opacity:var(--p-disabled-opacity, .5);cursor:not-allowed}.choice-title{display:flex;align-items:center;justify-content:space-between;gap:var(--p-space-2, 8px);width:100%;font-weight:600;line-height:1.35}.choice-title mat-icon{color:var(--md-sys-color-primary);flex-shrink:0;font-size:20px;height:20px;width:20px}.choice-description{font-size:var(--p-font-size-sm, 13px);line-height:1.5;color:var(--md-sys-color-on-surface-variant)}.choice-meta{font-size:var(--p-font-size-xs, 12px);color:var(--md-sys-color-on-surface-variant);margin-top:auto;padding-top:var(--p-space-1, 4px)}.theme-sample{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));padding:var(--p-space-3, 12px);border-radius:var(--p-radius-md, 8px);min-height:128px;align-self:stretch;margin-bottom:var(--p-space-1, 4px)}.sample-metric,.sample-chart{background:var(--sample-surface);border-radius:var(--sample-radius);box-shadow:var(--sample-shadow);padding:var(--p-space-2, 8px)}.sample-metric b{display:block;font-size:var(--p-font-size-md, 16px)}.sample-metric i{display:block;height:3px;width:55%;margin-top:var(--p-space-1, 4px);background:var(--sample-accent)}.sample-chart{grid-column:1/-1;display:flex;height:40px;align-items:flex-end;gap:var(--p-space-2, 8px)}.sample-chart i{flex:1;background:var(--sample-accent);height:50%;border-radius:var(--p-radius-xs, 2px)}.sample-chart i:nth-child(2){height:85%}.sample-chart i:nth-child(3){height:65%}.sample-chart i:nth-child(4){height:100%}.sample-chart i:nth-child(5){height:75%}.shell-options,.arrangement-rules{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:var(--p-space-3, 12px)}.studio-panel summary{cursor:pointer;font-weight:500;line-height:1.5;padding-block:var(--p-space-2, 8px)}.shell-gallery{margin-top:var(--p-space-4, 16px)}.shell-sample{display:block;align-self:stretch;border:1px solid;overflow:hidden;margin:var(--p-space-2, 8px)}.shell-sample>span{display:flex;gap:var(--p-space-2, 8px);align-items:center;padding:var(--p-space-2, 8px);border-bottom:1px solid transparent}.shell-sample i{height:4px;width:60%;background:var(--md-sys-color-on-surface-variant);opacity:.4}.shell-sample>span:last-child{min-height:30px;flex-direction:column;align-items:flex-start}.shell-sample>span:last-child i:last-child{width:40%}.selection-line{margin:0;font-size:var(--p-font-size-sm, 13px);color:var(--md-sys-color-on-surface-variant)}.studio-actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--p-space-2, 8px)}.distribution-sample{display:grid;grid-template-rows:repeat(2,1fr);gap:var(--p-space-1, 4px);align-self:stretch;height:76px;padding:var(--p-space-2, 8px);background:var(--md-sys-color-surface-container);border-radius:var(--p-radius-md, 8px)}.distribution-sample i{display:grid;place-items:center;background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);border-radius:var(--p-radius-sm, 4px);font-size:var(--p-font-size-xs, 12px);font-style:normal}.distribution-sample i:nth-child(n+4){opacity:.65}.selected .distribution-sample i{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.arrangement-review{display:grid;gap:var(--p-space-3, 12px);padding-block:var(--p-space-4, 16px);border-top:2px solid var(--md-sys-color-primary)}.review-heading{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:var(--p-space-2, 8px)}.review-heading>span{font-size:var(--p-font-size-sm, 13px);color:var(--md-sys-color-primary)}.canvas-comparison{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-3, 12px)}.canvas-comparison figure{margin:0;min-width:0}.canvas-comparison figcaption{font-weight:500;margin-bottom:var(--p-space-2, 8px)}.canvas-map{display:grid;gap:2px;height:220px;padding:var(--p-space-2, 8px);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);border-radius:var(--p-radius-md, 8px)}.canvas-map span{display:grid;place-items:center;min-height:0;min-width:0;overflow:hidden;font-size:var(--p-font-size-xs, 12px);color:var(--md-sys-color-on-secondary-container);background:var(--md-sys-color-secondary-container);border-radius:var(--p-radius-xs, 2px)}.canvas-map .changed{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.widget-changes{padding-inline-start:var(--p-space-5, 20px);margin:0}.widget-changes li{padding-block:var(--p-space-2, 8px);overflow-wrap:anywhere}.widget-changes span{display:block;font-size:var(--p-font-size-sm, 13px);color:var(--md-sys-color-on-surface-variant)}.studio-status{border-inline-start:3px solid var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low);padding:var(--p-space-3, 12px);margin:0;line-height:1.5}@container (max-width:420px){.studio-gallery{grid-template-columns:minmax(0,1fr)}.canvas-map{height:180px}}@media(prefers-reduced-motion:reduce){.studio-choice{transition:none}}.arrangement-feedback:empty{display:none}.arrangement-feedback:focus{outline:none}.arrangement-feedback:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:3px}@container (min-width:620px){.arrangement-gallery{grid-template-columns:repeat(3,minmax(0,1fr))}}:host ::ng-deep .studio-tabs>.mat-mdc-tab-header{position:sticky;top:0;z-index:var(--praxis-layer-sticky-local, 100);background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface))}:host ::ng-deep .studio-tabs>.mat-mdc-tab-header .mdc-tab{flex:1 1 0;padding-inline:var(--p-space-1, 4px)}.arrangement-feedback{scroll-margin-block-start:var(--p-space-16, 64px)}\n"], dependencies: [{ kind: "component", type: PagePresetComparisonComponent, selector: "praxis-page-preset-comparison", inputs: ["expanded", "layoutId", "themeId", "layoutPresetOptions", "canvas"], outputs: ["choose"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i3$1.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i3$1.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i9.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i9.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i8.MatTabContent, selector: "[matTabContent]" }, { kind: "component", type: i8.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i8.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13598
14134
  }
13599
14135
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicPageConfigEditorComponent, decorators: [{
13600
14136
  type: Component,
13601
- args: [{ selector: 'praxis-dynamic-page-config-editor', standalone: true, imports: [ReactiveFormsModule, MatButtonModule, MatButtonToggleModule, MatFormFieldModule, MatIconModule, MatInputModule, MatSelectModule, MatTabsModule, MatTooltipModule, PraxisIconButtonComponent], providers: [providePraxisPageBuilderI18n()], template: `
13602
- <div class="editor">
13603
- @if (!isHostedInSettingsPanel) { <div class="head">
13604
- <div>{{ tx('editor.title', 'Page settings') }}</div>
13605
- <button mat-stroked-button type="button" (click)="reset()">{{ tx('editor.reload', 'Reload') }}</button>
13606
- </div> }
13607
- <details class="summary-details">
13608
- <summary>{{ tx('editor.summaryAria', 'Configuration summary') }}</summary>
13609
- <div class="summary">
13610
- @if (!isHostedInSettingsPanel) {
13611
- <span>{{ validationStateLabel }}</span>
13612
- <span>{{ dirtyStateLabel }}</span>
13613
- }
13614
- <span>{{ selectedLayoutPresetLabel }}</span>
13615
- <span>{{ selectedThemePresetLabel }}</span>
13616
- <span>{{ canvasSummaryLabel }}</span>
13617
- </div>
13618
- </details>
13619
- @if (nonLimitError) { <p class="limits-error" role="alert">{{ nonLimitError }}</p> }
13620
- @if (activeTab() === 'limits' && canvasLimits.targets.length) {
13621
- <div class="selection-identity" data-testid="canvas-limits-identity">
13622
- <mat-icon aria-hidden="true">dashboard_customize</mat-icon>
13623
- <strong>{{ limitsTargetLabel(limitsTargetControl.value) }}</strong>
13624
- </div>
13625
- }
13626
- <mat-tab-group class="editor-tabs" [mat-stretch-tabs]="false" [selectedIndex]="selectedTabIndex" (selectedIndexChange)="onSelectedTabIndexChange($event)">
13627
- <mat-tab [label]="tx('editor.tabs.layout', 'Layout')"><div class="grid"><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.cols', 'Columns') }}</mat-label><input matInput type="number" min="1" [formControl]="canvasColumnsControl" /></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.rowUnit', 'Row unit') }}</mat-label><input matInput [formControl]="canvasRowUnitControl" /></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.gap', 'Gap') }}</mat-label><input matInput [formControl]="canvasGapControl" /></mat-form-field><mat-form-field class="page-inset-field" appearance="outline"><mat-label>{{ tx('editor.layout.paddingInline', 'Page side inset') }}</mat-label><input matInput [formControl]="paddingInlineControl" placeholder="24px" /><button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="tx('editor.hints.paddingInline', 'Shared space beside all page widgets. Use a CSS length or theme token; 0px removes it. Leave empty to inherit the host theme.')" [attr.aria-label]="tx('editor.layout.paddingInline', 'Page side inset')"></button></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.autoRows', 'Auto rows') }}</mat-label><mat-select [formControl]="canvasAutoRowsControl"><mat-option value="fixed">{{ tx('editor.layout.autoRowsFixed', 'Fixed height') }}</mat-option><mat-option value="content">{{ tx('editor.layout.autoRowsContent', 'Fit content') }}</mat-option></mat-select></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.layoutPreset', 'Layout preset') }}</mat-label><mat-select [formControl]="layoutPresetControl"><mat-option value="">{{ tx('editor.layout.none', 'None') }}</mat-option>@for (preset of layoutPresetEntries; track preset.id) {<mat-option [value]="preset.id">{{ preset.label }}</mat-option>}</mat-select></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.themePreset', 'Theme preset') }}</mat-label><mat-select [formControl]="themePresetControl"><mat-option value="">{{ tx('editor.layout.layoutDefault', 'Layout default') }}</mat-option>@for (preset of themePresetEntries; track preset.id) {<mat-option [value]="preset.id">{{ preset.label }}</mat-option>}</mat-select></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.shellPreset', 'Shell visual preset') }}</mat-label><mat-select [formControl]="shellPresetControl"><mat-option value="">{{ tx('editor.layout.none', 'None') }}</mat-option>@for (preset of shellPresetEntries; track preset.id) {<mat-option [value]="preset.id">{{ shellPresetLabel(preset) }}</mat-option>}</mat-select></mat-form-field></div>@if (selectedLayoutPresetEntry; as preset) {<div class="preset-preview"><div class="preset-preview__head"><div><span class="eyebrow">{{ tx('editor.preview.layoutEyebrow', 'Layout template') }} · {{ preset.category }}</span><strong>{{ preset.label }}</strong></div><span>{{ layoutPresetSummary(preset) }}</span></div><p>{{ preset.description || tx('editor.preview.layoutFallbackDescription', 'Page organization preset.') }}</p><div class="preset-preview__chips"><span>{{ slotsSummary(preset) }}</span><span>{{ requiredSlotsSummary(preset) }}</span><span>{{ devicePolicySummary(preset) }}</span></div>@if (recommendedThemePresetEntry; as theme) {<div class="preset-preview__recommendation"><div><span class="eyebrow">{{ tx('editor.preview.inheritedTheme', 'Inherited theme') }}</span><strong>{{ theme.label }}</strong><span>{{ inheritedThemeHelp(theme) }}</span></div><button mat-stroked-button type="button" [disabled]="themePresetControl.value === theme.id" (click)="pinRecommendedThemePreset()">{{ tx('editor.preview.pinTheme', 'Pin theme') }}</button></div>}</div>} @if (selectedThemePresetEntry; as theme) {<div class="preset-preview preset-preview--theme"><div class="preset-preview__head"><div><span class="eyebrow">{{ tx('editor.preview.explicitTheme', 'Explicit theme') }}</span><strong>{{ theme.label }}</strong></div><span>{{ theme.density }} · {{ theme.motion }}</span></div><p>{{ theme.description || tx('editor.preview.themeFallbackDescription', 'Page visual preset.') }}</p><div class="preset-preview__chips"><span>{{ tx('editor.preview.shellChip', 'Shell: {{value}}').replace('{{value}}', theme.shellPreset) }}</span><span>{{ tx('editor.preview.chartsChip', 'Charts: {{value}}').replace('{{value}}', theme.chartThemePreset) }}</span></div></div>}</mat-tab>
13628
- <mat-tab [label]="tx('editor.tabs.grouping', 'Grouping')"><div class="panel">
13629
- <p class="hint" data-testid="canvas-grouping-notice">{{ tx('editor.grouping.canvasNotice', 'Canvas positions control this page. Groups below are preserved for flow layouts and do not arrange widgets in the canvas. Use the widget move and resize controls to place them side by side.') }}</p><div class="head"><span>{{ tx('editor.grouping.title', 'Grouping') }}</span><button mat-stroked-button type="button" (click)="addGroupingNode()"><mat-icon>add</mat-icon>{{ tx('editor.grouping.add', 'Add group') }}</button></div>@for (group of groupingNodes.controls; track $index) {<div class="card" [formGroup]="group"><div class="head"><span>{{ groupLabel($index) }}</span><button praxisIconButton="delete" size="compact" type="button" [attr.aria-label]="tx('editor.grouping.removeAria', 'Remove group')" (click)="removeGroupingNode($index)"></button></div><div class="grid"><mat-form-field appearance="outline"><mat-label>{{ tx('editor.grouping.kind', 'Kind') }}</mat-label><mat-select formControlName="kind"><mat-option value="section">section</mat-option><mat-option value="tabs">tabs</mat-option><mat-option value="hero">hero</mat-option><mat-option value="rail">rail</mat-option></mat-select></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.grouping.id', 'Id') }}</mat-label><input matInput formControlName="id" /></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.grouping.label', 'Label') }}</mat-label><input matInput formControlName="label" /></mat-form-field>@if (group.controls.kind.value === 'section') {<mat-form-field appearance="outline"><mat-label>{{ tx('editor.grouping.layout', 'Layout') }}</mat-label><mat-select formControlName="layout"><mat-option value="">{{ tx('editor.grouping.layoutDefault', 'Default') }}</mat-option><mat-option value="stack">stack</mat-option><mat-option value="grid">grid</mat-option><mat-option value="row">row</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'hero') {<mat-form-field appearance="outline"><mat-label>{{ tx('editor.grouping.emphasis', 'Emphasis') }}</mat-label><mat-select formControlName="emphasis"><mat-option value="">{{ tx('editor.grouping.layoutDefault', 'Default') }}</mat-option><mat-option value="high">high</mat-option><mat-option value="medium">medium</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'rail') {<mat-form-field appearance="outline"><mat-label>{{ tx('editor.grouping.side', 'Side') }}</mat-label><mat-select formControlName="side"><mat-option value="left">left</mat-option><mat-option value="right">right</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'tabs') {<mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.grouping.tabsJson', 'Tabs (JSON)') }}</mat-label><textarea matInput rows="4" formControlName="tabsJson"></textarea></mat-form-field>} @else {<mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.grouping.widgetKeysCsv', 'Widget keys (comma-separated)') }}</mat-label><input matInput formControlName="widgetKeysCsv" /></mat-form-field>}</div></div>}</div></mat-tab>
13630
- <mat-tab [label]="tx('editor.tabs.devices', 'Device Layouts')">
13631
- <div class="panel">
13632
- <mat-button-toggle-group [value]="activeDevice()" (change)="activeDevice.set($event.value)" [attr.aria-label]="tx('editor.devices.target', 'Device to configure')">
13633
- @for (device of deviceKeys; track device) {
13634
- <mat-button-toggle [value]="device">{{ deviceLabels[device] }}</mat-button-toggle>
13635
- }
13636
- </mat-button-toggle-group>
13637
- @for (device of deviceKeys; track device) {
13638
- @if (activeDevice() === device) {
13639
- <div class="card">
13640
- <div class="head"><strong>{{ deviceLabels[device] }}</strong><button mat-stroked-button type="button" [attr.data-testid]="'stack-device-' + device" [disabled]="!hasCanvasWidgets" (click)="stackDevice(device)"><mat-icon aria-hidden="true">view_agenda</mat-icon>{{ tx('editor.devices.stack', 'Stack widgets') }}</button></div>
13641
- <p class="hint">{{ tx('editor.devices.stackHelp', 'Use full width and natural height, in reading order. This replaces positions and width limits only for this device; other layouts stay unchanged.') }}</p>
13642
- @if (stackedDevices.has(device)) { <p class="hint" role="status">{{ tx('editor.devices.stacked', 'Single-column layout prepared. Save to apply; Reset undoes the changes.') }}</p> }
13643
- <div class="grid">
13644
- <mat-form-field appearance="outline"><mat-label>{{ tx('editor.devices.columns', 'Columns') }}</mat-label><input matInput type="number" min="1" [formControl]="deviceForms[device].controls.columns" /></mat-form-field>
13645
- <mat-form-field class="page-inset-field" appearance="outline"><mat-label>{{ tx('editor.layout.paddingInline', 'Page side inset') }}</mat-label><input matInput [formControl]="deviceForms[device].controls.paddingInline" placeholder="16px" /></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.devices.gap', 'Gap') }}</mat-label><input matInput [formControl]="deviceForms[device].controls.gap" /></mat-form-field>
13646
- <mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.autoRows', 'Auto rows') }}</mat-label><mat-select [formControl]="deviceForms[device].controls.autoRows"><mat-option value="">{{ tx('editor.devices.orientationDefault', 'Default') }}</mat-option><mat-option value="fixed">{{ tx('editor.layout.autoRowsFixed', 'Fixed height') }}</mat-option><mat-option value="content">{{ tx('editor.layout.autoRowsContent', 'Fit content') }}</mat-option></mat-select></mat-form-field>
13647
- </div>
13648
- <details><summary>{{ tx('editor.devices.advanced', 'Advanced overrides') }}</summary><div class="grid">
13649
- <mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.rowUnit', 'Row unit') }}</mat-label><input matInput [formControl]="deviceForms[device].controls.rowUnit" /></mat-form-field>
13650
- <mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.devices.groupingOverridesJson', 'Grouping overrides (JSON)') }}</mat-label><textarea matInput rows="4" [formControl]="deviceForms[device].controls.groupingOverrides"></textarea></mat-form-field>
13651
- <mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.devices.widgetOverridesJson', 'Widget overrides (JSON)') }}</mat-label><textarea matInput rows="4" [formControl]="deviceForms[device].controls.widgetOverrides"></textarea></mat-form-field>
13652
- </div></details>
13653
- @if (!stackedDevices.has(device)) { <div class="hint">{{ tx('editor.preview.deviceCanvasHint', 'Existing canvas item overrides are preserved.') }}</div> }
13654
- </div>
13655
- }
13656
- }
13657
- </div>
13658
- </mat-tab>
13659
- <mat-tab [label]="tx('editor.tabs.context', 'Context')"><mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.context.json', 'Context (JSON)') }}</mat-label><textarea matInput rows="12" [formControl]="contextControl"></textarea></mat-form-field></mat-tab>
13660
- <mat-tab [label]="tx('editor.tabs.state', 'Page State')"><mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.state.valuesJson', 'Primary values (JSON)') }}</mat-label><textarea matInput rows="14" [formControl]="stateControl"></textarea></mat-form-field></mat-tab>
13661
- <mat-tab [label]="tx('editor.limits.tab', 'Widget limits')">
13662
- <section class="limits-panel" data-testid="canvas-limits-editor">
13663
- <p class="hint" id="canvas-limits-help">{{ tx('editor.limits.help', 'Limits use grid columns and rows, not pixels. Leave blank for defaults, or to inherit the base limits on a device.') }}</p>
13664
- @if (canvasLimits.targets.length) {
13665
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13666
- <mat-label>{{ tx('editor.limits.target', 'Widget and layout') }}</mat-label>
13667
- <mat-select [formControl]="limitsTargetControl" data-testid="canvas-limits-target">
13668
- @for (target of canvasLimits.targets; track target.scope + ':' + target.key; let index = $index) {
13669
- <mat-option [value]="index">{{ limitsTargetLabel(index) }}</mat-option>
13670
- }
13671
- </mat-select>
13672
- </mat-form-field>
13673
- <p class="hint">{{ limitsGeometryLabel() }}</p>
13674
- @if (canvasLimits.form.controls[limitsTargetControl.value]; as limitsForm) {
13675
- <div class="limits-fields" aria-describedby="canvas-limits-help">
13676
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13677
- <mat-label>{{ tx('editor.limits.minColSpan', 'Minimum width (columns)') }}</mat-label>
13678
- <input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.minColSpan" data-testid="canvas-limit-minColSpan">
13679
- <mat-error>{{ limitFieldError(limitsForm.controls.minColSpan) }}</mat-error>
13680
- </mat-form-field>
13681
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13682
- <mat-label>{{ tx('editor.limits.maxColSpan', 'Maximum width (columns)') }}</mat-label>
13683
- <input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.maxColSpan" data-testid="canvas-limit-maxColSpan">
13684
- <mat-error>{{ limitFieldError(limitsForm.controls.maxColSpan) }}</mat-error>
13685
- </mat-form-field>
13686
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13687
- <mat-label>{{ tx('editor.limits.minRowSpan', 'Minimum height (rows)') }}</mat-label>
13688
- <input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.minRowSpan" data-testid="canvas-limit-minRowSpan">
13689
- <mat-error>{{ limitFieldError(limitsForm.controls.minRowSpan) }}</mat-error>
13690
- </mat-form-field>
13691
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13692
- <mat-label>{{ tx('editor.limits.maxRowSpan', 'Maximum height (rows)') }}</mat-label>
13693
- <input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.maxRowSpan" data-testid="canvas-limit-maxRowSpan">
13694
- <mat-error>{{ limitFieldError(limitsForm.controls.maxRowSpan) }}</mat-error>
13695
- </mat-form-field>
13696
- </div>
13697
- @if (limitsUseContentHeight()) {
13698
- <p class="limits-notice"><mat-icon aria-hidden="true">height</mat-icon><span>{{ tx('editor.limits.autoHeight', 'Choose natural height or an adjustable height with internal scrolling.') }}</span></p>
13699
- <mat-button-toggle-group class="height-modes" [formControl]="limitsForm.controls.heightMode" [attr.aria-label]="tx('editor.height.mode', 'Widget height')" data-testid="canvas-height-mode">
13700
- <mat-button-toggle value="inherit">{{ tx('editor.height.inherit', 'Default') }}</mat-button-toggle>
13701
- <mat-button-toggle value="auto">{{ tx('editor.height.auto', 'Automatic') }}</mat-button-toggle>
13702
- <mat-button-toggle value="fixed">{{ tx('editor.height.fixed', 'Adjustable') }}</mat-button-toggle>
13703
- </mat-button-toggle-group>
13704
- @if (limitsForm.controls.heightMode.value === 'fixed') {
13705
- <div class="limits-fields">
13706
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13707
- <mat-label>{{ tx('editor.height.value', 'Height (px)') }}</mat-label>
13708
- <input matInput type="number" min="160" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.heightPx" data-testid="canvas-height-px">
13709
- <mat-error>{{ limitFieldError(limitsForm.controls.heightPx) }}</mat-error>
13710
- </mat-form-field>
13711
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13712
- <mat-label>{{ tx('editor.height.top', 'Top inset (px)') }}</mat-label>
13713
- <input matInput type="number" min="0" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.offsetTopPx" data-testid="canvas-height-top">
13714
- <mat-error>{{ limitFieldError(limitsForm.controls.offsetTopPx) }}</mat-error>
13715
- </mat-form-field>
13716
- </div>
13717
- }
13718
- <div class="limits-fields">
13719
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13720
- <mat-label>{{ tx('editor.height.min', 'Minimum height (px)') }}</mat-label>
13721
- <input matInput type="number" min="160" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.minHeightPx" data-testid="canvas-limit-minHeightPx">
13722
- <mat-error>{{ limitFieldError(limitsForm.controls.minHeightPx) }}</mat-error>
13723
- </mat-form-field>
13724
- <mat-form-field appearance="outline" subscriptSizing="dynamic">
13725
- <mat-label>{{ tx('editor.height.max', 'Maximum height (px)') }}</mat-label>
13726
- <input matInput type="number" min="160" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.maxHeightPx" data-testid="canvas-limit-maxHeightPx">
13727
- <mat-error>{{ limitFieldError(limitsForm.controls.maxHeightPx) }}</mat-error>
13728
- </mat-form-field>
13729
- </div>
13730
- }
13731
- }
13732
- @if (limitsSummaryError) { <p class="limits-error" role="alert">{{ limitsSummaryError }}</p> }
13733
- } @else {
13734
- <p>{{ tx('editor.limits.empty', 'Add widgets to the canvas to configure their limits.') }}</p>
13735
- }
13736
- </section>
13737
- </mat-tab>
13738
- </mat-tab-group>
13739
- </div>
13740
- `, changeDetection: ChangeDetectionStrategy.OnPush, host: { '(window:resize)': 'refreshRenderedValidation()' }, styles: [".page-inset-field{grid-column:1 / -1;min-width:0}:host{display:block;min-width:0;max-width:100%}.height-modes{width:fit-content;max-width:100%;flex-wrap:wrap;margin-bottom:16px}.editor,.panel{display:grid;gap:16px;min-width:0;max-width:100%;color:var(--md-sys-color-on-surface, currentColor)}mat-tab-group,mat-form-field{min-width:0;max-width:100%}.head,.summary{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.summary{color:var(--md-sys-color-on-surface-variant, currentColor)}.selection-identity{position:sticky;top:0;z-index:var(--praxis-layer-sticky-local, 100);display:flex;align-items:flex-start;gap:8px;padding-block:8px;background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface));overflow-wrap:anywhere}.selection-identity mat-icon{flex-shrink:0}:host ::ng-deep .editor-tabs>.mat-mdc-tab-header .mdc-tab{min-width:0;padding-inline:12px}:host ::ng-deep .editor-tabs>.mat-mdc-tab-header .mdc-tab__text-label{max-width:128px;white-space:normal;overflow-wrap:anywhere;text-align:center;letter-spacing:normal;line-height:1.25}.grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr))}@media(max-width:600px){.grid{grid-template-columns:minmax(0,1fr)}.head,.summary,.preset-preview__head,.preset-preview__recommendation{align-items:stretch}.head>button,.preset-preview__recommendation button{width:100%}}.card{display:grid;gap:12px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 18%, transparent));border-radius:8px;background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface-container, transparent));color:var(--md-sys-color-on-surface, currentColor)}.limits-panel{display:grid;gap:8px;min-width:0}.limits-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:4px 16px}.limits-fields mat-form-field{min-width:0}.limits-notice{display:flex;gap:8px;align-items:flex-start;color:var(--md-sys-color-on-surface-variant, currentColor)}.limits-notice mat-icon{flex-shrink:0}.limits-error{color:var(--md-sys-color-error, #b3261e)}.wide{grid-column:1 / -1}.hint{color:var(--md-sys-color-on-surface-variant, currentColor);font-size:12px}.preset-preview{display:grid;gap:12px;margin-top:12px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 18%, transparent));border-radius:8px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low, transparent) 88%,var(--md-sys-color-primary-container, transparent) 12%)}.preset-preview--theme{background:var(--md-sys-color-surface-container-low, transparent)}.preset-preview__head,.preset-preview__recommendation{display:flex;justify-content:space-between;align-items:center;gap:16px}.preset-preview__head>div,.preset-preview__recommendation>div{display:grid;gap:4px;min-width:0}.preset-preview p{margin:0;color:var(--md-sys-color-on-surface-variant, currentColor);line-height:1.45}.preset-preview__chips{display:flex;flex-wrap:wrap;gap:8px}.preset-preview__chips span{padding:5px 9px;border-radius:999px;background:var(--md-sys-color-surface-container, color-mix(in srgb, currentColor 8%, transparent));color:var(--md-sys-color-on-surface-variant, currentColor);font-size:12px;line-height:1.3}.preset-preview__recommendation{padding-top:12px;border-top:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 14%, transparent))}.preset-preview__recommendation span:last-child{color:var(--md-sys-color-on-surface-variant, currentColor);font-size:13px;line-height:1.35}.eyebrow{color:var(--md-sys-color-primary, currentColor);font-size:11px;font-weight:700;letter-spacing:0;text-transform:uppercase}:host ::ng-deep .mat-mdc-tab-body-content{padding-top:12px;box-sizing:border-box}\n"] }]
14137
+ args: [{ selector: 'praxis-dynamic-page-config-editor', standalone: true, imports: [PagePresetComparisonComponent, ReactiveFormsModule, MatButtonModule, MatButtonToggleModule, MatFormFieldModule, MatIconModule, MatInputModule, MatSelectModule, MatTabsModule, MatTooltipModule, PraxisIconButtonComponent], providers: [providePraxisPageBuilderI18n()], changeDetection: ChangeDetectionStrategy.OnPush, host: { '(window:resize)': 'refreshRenderedValidation()' }, template: "<div class=\"editor\">\n @if (!isHostedInSettingsPanel) { <div class=\"head\">\n <div>{{ tx('editor.title', 'Page settings') }}</div>\n <button mat-stroked-button type=\"button\" (click)=\"reset()\">{{ tx('editor.reload', 'Reload') }}</button>\n </div> }\n\n @if (nonLimitError) { <p class=\"limits-error\" role=\"alert\">{{ nonLimitError }}</p> }\n <mat-tab-group class=\"editor-tabs studio-tabs\" [mat-stretch-tabs]=\"false\" [selectedIndex]=\"workspaceTab()\" (selectedIndexChange)=\"workspaceTab.set($event)\">\n <mat-tab [label]=\"tx('studio.visualTab', 'Visual templates')\"><ng-template matTabContent>\n <section class=\"studio-panel\" data-testid=\"studio-visual\">\n <header class=\"studio-heading\"><span class=\"eyebrow\">{{ tx('studio.visualEyebrow', 'Appearance') }}</span><h2>{{ tx('studio.visualTitle', 'Give the page a visual language') }}</h2><p>{{ tx('studio.visualHelp', 'Choose surfaces, density and chart styling together. Individual widget overrides still take precedence.') }}</p></header>\n <button mat-stroked-button type=\"button\" [attr.aria-pressed]=\"!themePresetControl.value\" (click)=\"themePresetControl.setValue('')\">{{ tx('editor.layout.layoutDefault', 'Layout default') }}</button>\n <p class=\"selection-line\">{{ tx('presets.effectiveTheme', 'Effective page theme') }}: <strong>{{ selectedThemePresetEntry?.label || themePresetControl.value || recommendedThemePresetEntry?.label || tx('presets.hostTheme', 'Host theme') }}</strong></p>\n <div class=\"studio-gallery\">\n @for (theme of themePresetEntries; track theme.id) {\n <button type=\"button\" class=\"studio-choice\" [class.selected]=\"themePresetControl.value === theme.id\" [attr.aria-pressed]=\"themePresetControl.value === theme.id\" [attr.data-theme-preset]=\"theme.id\" (click)=\"themePresetControl.setValue(theme.id)\">\n <span class=\"theme-sample\" aria-hidden=\"true\" [style.background]=\"themeTokens(theme.id)['--pdx-page-bg']\" [style.--sample-accent]=\"themeTokens(theme.id)['--pdx-page-accent']\" [style.--sample-surface]=\"themeTokens(theme.id)['--pdx-page-surface']\" [style.--sample-radius]=\"themeTokens(theme.id)['--pdx-page-card-radius']\" [style.--sample-shadow]=\"themeTokens(theme.id)['--pdx-page-card-shadow']\" [style.gap]=\"themeTokens(theme.id)['--pdx-page-gap']\">\n <span class=\"sample-metric\"><b>128</b><i></i></span><span class=\"sample-metric\"><b>86%</b><i></i></span><span class=\"sample-chart\"><i></i><i></i><i></i><i></i><i></i></span>\n </span>\n <span class=\"choice-title\">{{ theme.label }}@if (themePresetControl.value === theme.id) {<mat-icon aria-hidden=\"true\">check_circle</mat-icon>}</span>\n <span class=\"choice-description\">{{ tx('studio.theme.' + theme.id, theme.label) }}</span>\n <span class=\"choice-meta\">{{ tx('studio.density.' + theme.density, theme.density) }} \u00B7 {{ shellName(theme.shellPreset) }}</span>\n </button>\n }\n </div>\n <p class=\"hint\">{{ tx('studio.samplesHelp', 'Illustrative samples using the preset tokens. Apply to see the result with your content.') }}</p>\n <details class=\"shell-options\"><summary>{{ tx('studio.shellTitle', 'Customize widget surfaces') }} \u00B7 {{ shellPresetEntries.length }}</summary>\n <p>{{ tx('studio.shellHelp', 'Override the page theme\u2019s default widget surface. Per-widget appearance settings keep their priority.') }}</p>\n <button mat-stroked-button type=\"button\" [attr.aria-pressed]=\"!shellPresetControl.value\" (click)=\"shellPresetControl.setValue('')\">{{ tx('editor.layout.inheritShell', 'Inherit appearance') }}</button>\n <div class=\"studio-gallery shell-gallery\">@for (shell of shellPresetEntries; track shell.id) {\n <button type=\"button\" class=\"studio-choice\" [class.selected]=\"shellPresetControl.value === shell.id\" [attr.aria-pressed]=\"shellPresetControl.value === shell.id\" (click)=\"shellPresetControl.setValue(shell.id)\">\n <span class=\"shell-sample\" aria-hidden=\"true\" [style.background]=\"shell.appearance.card?.background\" [style.border-color]=\"shell.appearance.card?.borderColor\" [style.border-radius]=\"shell.appearance.card?.borderRadius\" [style.box-shadow]=\"shell.appearance.card?.shadow\"><span [style.background]=\"shell.appearance.header?.background\" [style.border-color]=\"shell.appearance.header?.borderColor\"><mat-icon>analytics</mat-icon><i></i></span><span [style.background]=\"shell.appearance.body?.background\"><i></i><i></i></span></span>\n <span class=\"choice-title\">{{ shellPresetLabel(shell) }}@if (shellPresetControl.value === shell.id) {<mat-icon aria-hidden=\"true\">check_circle</mat-icon>}</span><span class=\"choice-description\">{{ shellDescription(shell) }}</span>\n </button>\n }</div>\n </details>\n </section>\n </ng-template></mat-tab>\n <mat-tab [label]=\"tx('studio.layoutTab', 'Layout templates')\"><ng-template matTabContent>\n <section class=\"studio-panel\" data-testid=\"studio-layout\">\n <header class=\"studio-heading\"><span class=\"eyebrow\">{{ tx('studio.layoutEyebrow', 'Composition model') }}</span><h2>{{ tx('studio.layoutTitle', 'Choose the structure for your use case') }}</h2><p>{{ tx('studio.layoutHelp', 'Compare the roles and default theme of each model. To redistribute the widgets already on this canvas, open Arrange.') }}</p></header>\n <div class=\"studio-actions\"><button mat-stroked-button type=\"button\" (click)=\"layoutPresetControl.setValue('')\">{{ tx('studio.inheritLayout', 'Use inherited model') }}</button><button mat-button type=\"button\" (click)=\"workspaceTab.set(2)\">{{ tx('studio.goArrange', 'Arrange existing widgets') }}<mat-icon iconPositionEnd>arrow_forward</mat-icon></button></div>\n <praxis-page-preset-comparison [expanded]=\"true\" [layoutId]=\"layoutPresetControl.value\" [layoutPresetOptions]=\"layoutPresetOptions\" [themeId]=\"themePresetControl.value\" [canvas]=\"true\" (choose)=\"layoutPresetControl.setValue($event)\" />\n </section>\n </ng-template></mat-tab>\n <mat-tab [label]=\"tx('studio.arrangeTab', 'Arrange')\"><ng-template matTabContent>\n <section class=\"studio-panel\" data-testid=\"canvas-arrangement\">\n <header class=\"studio-heading\"><span class=\"eyebrow\">{{ tx('studio.arrangeEyebrow', 'Existing widgets') }} \u00B7 {{ canvasColumnsControl.value }} {{ tx('editor.layout.cols', 'Columns') }}</span><h2>{{ tx('studio.arrangeTitle', 'Find the right balance of space') }}</h2><p>{{ tx('studio.arrangeHelp', 'Choose a distribution, compare the real positions, then use it in the draft. Widgets keep their reading order and content.') }}</p></header>\n <div #arrangementFeedback tabindex=\"-1\" class=\"arrangement-feedback\">\n @if (arrangementPreview(); as preview) {\n <section class=\"arrangement-review\" aria-live=\"polite\" data-testid=\"arrangement-preview\">\n <div class=\"review-heading\"><h3>{{ tx('studio.reviewTitle', 'Compare before using') }}</h3><span>{{ preview.keys.length }} {{ tx('studio.changedWidgets', 'widgets change') }}</span></div>\n <div class=\"canvas-comparison\">\n @for (side of ['before', 'after']; track side) {\n <figure><figcaption>{{ side === 'before' ? tx('studio.before', 'Before') : tx('studio.after', 'After') }}</figcaption>\n <div class=\"canvas-map\" [style.grid-template-columns]=\"'repeat(' + preview.canvas.columns + ', minmax(0, 1fr))'\" [style.grid-template-rows]=\"'repeat(' + previewRowCount(preview.before, preview.canvas) + ', minmax(0, 1fr))'\" role=\"img\" [attr.aria-label]=\"side === 'before' ? tx('studio.before', 'Before') : tx('studio.after', 'After')\">\n @for (item of previewItems(side === 'before' ? preview.before : preview.canvas); track item.key) {\n <span [style.grid-column]=\"item.col + ' / span ' + item.colSpan\" [style.grid-row]=\"item.row + ' / span ' + item.rowSpan\" [class.changed]=\"preview.keys.includes(item.key)\" [matTooltip]=\"item.label\">{{ item.number }}</span>\n }\n </div>\n </figure>\n }\n </div>\n <p class=\"hint\">{{ tx('studio.mapHelp', 'Numbered map of your widgets. Positions and spans are to scale; content height is evaluated on the page.') }}</p>\n <details><summary>{{ tx('studio.widgetDetails', 'Widget positions and sizes') }}</summary><ol class=\"widget-changes\">@for (item of previewItems(preview.canvas); track item.key) {<li><strong>{{ item.label }}</strong><span>{{ tx('studio.before', 'Before') }}: {{ tx('editor.arrange.position', 'Column / row') }} {{ preview.before.items[item.key].col }} / {{ preview.before.items[item.key].row }} \u00B7 {{ tx('studio.gridSize', 'Size in columns \u00D7 rows') }} {{ preview.before.items[item.key].colSpan }} \u00D7 {{ preview.before.items[item.key].rowSpan }}</span><span>{{ tx('studio.after', 'After') }}: {{ tx('editor.arrange.position', 'Column / row') }} {{ item.col }} / {{ item.row }} \u00B7 {{ tx('studio.gridSize', 'Size in columns \u00D7 rows') }} {{ item.colSpan }} \u00D7 {{ item.rowSpan }}</span></li>}</ol></details>\n <button mat-flat-button type=\"button\" data-testid=\"arrangement-use\" (click)=\"useArrangement()\">{{ tx('editor.arrange.use', 'Use arrangement in draft') }}<mat-icon iconPositionEnd>arrow_forward</mat-icon></button>\n </section>\n }\n @if (arrangementMessage()) {<p class=\"studio-status\" role=\"status\">{{ arrangementMessage() }}</p>}\n @if (arrangementUndoAvailable()) {<button mat-stroked-button type=\"button\" data-testid=\"arrangement-undo\" (click)=\"undoArrangement()\"><mat-icon>undo</mat-icon>{{ tx('editor.arrange.undo', 'Undo arrangement') }}</button>}\n </div>\n <div class=\"studio-gallery arrangement-gallery\">\n @for (choice of arrangementChoices; track choice.id) {\n <button type=\"button\" class=\"studio-choice\" [class.selected]=\"arrangementSelection() === choice.id\" [attr.aria-pressed]=\"arrangementSelection() === choice.id\" [disabled]=\"!hasCanvasWidgets || !arrangementFits(choice.id)\" [attr.data-testid]=\"'arrange-' + choice.id\" (click)=\"prepareArrangement(choice.id)\">\n <span class=\"distribution-sample\" aria-hidden=\"true\" [style.grid-template-columns]=\"(arrangementFits(choice.id) ? arrangementWidths(choice.id) : choice.weights).join('fr ') + 'fr'\">@for (weight of choice.weights; track $index) {<i>{{ arrangementWidths(choice.id)[$index] }}</i>}@for (weight of choice.weights; track $index) {<i></i>}</span>\n <span class=\"choice-title\">{{ tx('editor.arrange.' + choice.id, choice.id) }}@if (arrangementSelection() === choice.id) {<mat-icon aria-hidden=\"true\">check_circle</mat-icon>}</span>\n <span class=\"choice-description\">{{ tx('studio.arrange.' + choice.id, choice.id) }}</span>\n <span class=\"choice-meta\">{{ arrangementFits(choice.id) ? arrangementWidths(choice.id).join(' + ') : tx('studio.moreColumns', 'Needs more grid columns') }}</span>\n </button>\n }\n </div>\n @if (!hasCanvasWidgets) {<p role=\"status\">{{ tx('editor.limits.empty', 'Add widgets to the canvas to configure their limits.') }}</p>}\n <details class=\"arrangement-rules\"><summary>{{ tx('studio.rules', 'Scope, devices and locked widgets') }}</summary><p>{{ tx('editor.arrange.help', 'Base canvas, in current reading order. Inheriting devices follow the base; explicit device positions are preserved. Locked positions stay in place.') }}</p><button mat-button type=\"button\" (click)=\"onSelectedTabIndexChange(5)\">{{ tx('studio.goLimits', 'Review widget limits') }}</button></details>\n </section>\n </ng-template></mat-tab>\n <mat-tab [label]=\"tx('studio.settingsTab', 'Adjustments')\">\n <div class=\"studio-panel\"><mat-tab-group class=\"editor-tabs\" [mat-stretch-tabs]=\"false\" [selectedIndex]=\"selectedTabIndex\" (selectedIndexChange)=\"selectDetailTab($event)\">\n <mat-tab [label]=\"tx('editor.tabs.layout', 'Layout')\"><div class=\"grid\"><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.cols', 'Columns') }}</mat-label><input matInput type=\"number\" min=\"1\" [formControl]=\"canvasColumnsControl\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.rowUnit', 'Row unit') }}</mat-label><input matInput [formControl]=\"canvasRowUnitControl\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.gap', 'Gap') }}</mat-label><input matInput [formControl]=\"canvasGapControl\" /></mat-form-field><mat-form-field class=\"page-inset-field\" appearance=\"outline\"><mat-label>{{ tx('editor.layout.paddingInline', 'Page side inset') }}</mat-label><input matInput [formControl]=\"paddingInlineControl\" placeholder=\"24px\" /><button praxisIconButton=\"help_outline\" size=\"dense\" matSuffix type=\"button\" [matTooltip]=\"tx('editor.hints.paddingInline', 'Shared space beside all page widgets. Use a CSS length or theme token; 0px removes it. Leave empty to inherit the host theme.')\" [attr.aria-label]=\"tx('editor.layout.paddingInline', 'Page side inset')\"></button></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.autoRows', 'Auto rows') }}</mat-label><mat-select [formControl]=\"canvasAutoRowsControl\"><mat-option value=\"fixed\">{{ tx('editor.layout.autoRowsFixed', 'Fixed height') }}</mat-option><mat-option value=\"content\">{{ tx('editor.layout.autoRowsContent', 'Fit content') }}</mat-option></mat-select></mat-form-field></div></mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.grouping', 'Grouping')\"><div class=\"panel\">\n <p class=\"hint\" data-testid=\"canvas-grouping-notice\">{{ tx('editor.grouping.canvasNotice', 'Canvas positions control this page. Groups below are preserved for flow layouts and do not arrange widgets in the canvas. Use the widget move and resize controls to place them side by side.') }}</p><div class=\"head\"><span>{{ tx('editor.grouping.title', 'Grouping') }}</span><button mat-stroked-button type=\"button\" (click)=\"addGroupingNode()\"><mat-icon>add</mat-icon>{{ tx('editor.grouping.add', 'Add group') }}</button></div>@for (group of groupingNodes.controls; track $index) {<div class=\"card\" [formGroup]=\"group\"><div class=\"head\"><span>{{ groupLabel($index) }}</span><button praxisIconButton=\"delete\" size=\"compact\" type=\"button\" [attr.aria-label]=\"tx('editor.grouping.removeAria', 'Remove group')\" (click)=\"removeGroupingNode($index)\"></button></div><div class=\"grid\"><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.kind', 'Kind') }}</mat-label><mat-select formControlName=\"kind\"><mat-option value=\"section\">section</mat-option><mat-option value=\"tabs\">tabs</mat-option><mat-option value=\"hero\">hero</mat-option><mat-option value=\"rail\">rail</mat-option></mat-select></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.id', 'Id') }}</mat-label><input matInput formControlName=\"id\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.label', 'Label') }}</mat-label><input matInput formControlName=\"label\" /></mat-form-field>@if (group.controls.kind.value === 'section') {<mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.layout', 'Layout') }}</mat-label><mat-select formControlName=\"layout\"><mat-option value=\"\">{{ tx('editor.grouping.layoutDefault', 'Default') }}</mat-option><mat-option value=\"stack\">stack</mat-option><mat-option value=\"grid\">grid</mat-option><mat-option value=\"row\">row</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'hero') {<mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.emphasis', 'Emphasis') }}</mat-label><mat-select formControlName=\"emphasis\"><mat-option value=\"\">{{ tx('editor.grouping.layoutDefault', 'Default') }}</mat-option><mat-option value=\"high\">high</mat-option><mat-option value=\"medium\">medium</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'rail') {<mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.side', 'Side') }}</mat-label><mat-select formControlName=\"side\"><mat-option value=\"left\">left</mat-option><mat-option value=\"right\">right</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'tabs') {<mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.grouping.tabsJson', 'Tabs (JSON)') }}</mat-label><textarea matInput rows=\"4\" formControlName=\"tabsJson\"></textarea></mat-form-field>} @else {<mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.grouping.widgetKeysCsv', 'Widget keys (comma-separated)') }}</mat-label><input matInput formControlName=\"widgetKeysCsv\" /></mat-form-field>}</div></div>}</div></mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.devices', 'Device Layouts')\">\n <div class=\"panel\">\n <mat-button-toggle-group [value]=\"activeDevice()\" (change)=\"activeDevice.set($event.value)\" [attr.aria-label]=\"tx('editor.devices.target', 'Device to configure')\">\n @for (device of deviceKeys; track device) {\n <mat-button-toggle [value]=\"device\">{{ deviceLabels[device] }}</mat-button-toggle>\n }\n </mat-button-toggle-group>\n @for (device of deviceKeys; track device) {\n @if (activeDevice() === device) {\n <div class=\"card\">\n <div class=\"head\"><strong>{{ deviceLabels[device] }}</strong><button mat-stroked-button type=\"button\" [attr.data-testid]=\"'stack-device-' + device\" [disabled]=\"!hasCanvasWidgets\" (click)=\"stackDevice(device)\"><mat-icon aria-hidden=\"true\">view_agenda</mat-icon>{{ tx('editor.devices.stack', 'Stack widgets') }}</button></div>\n <p class=\"hint\">{{ tx('editor.devices.stackHelp', 'Use full width and natural height, in reading order. This replaces positions and width limits only for this device; other layouts stay unchanged.') }}</p>\n @if (stackedDevices.has(device)) { <p class=\"hint\" role=\"status\">{{ tx('editor.devices.stacked', 'Single-column layout prepared. Save to apply; Reset undoes the changes.') }}</p> }\n <div class=\"grid\">\n <mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.devices.columns', 'Columns') }}</mat-label><input matInput type=\"number\" min=\"1\" [formControl]=\"deviceForms[device].controls.columns\" /></mat-form-field>\n <mat-form-field class=\"page-inset-field\" appearance=\"outline\"><mat-label>{{ tx('editor.layout.paddingInline', 'Page side inset') }}</mat-label><input matInput [formControl]=\"deviceForms[device].controls.paddingInline\" placeholder=\"16px\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.devices.gap', 'Gap') }}</mat-label><input matInput [formControl]=\"deviceForms[device].controls.gap\" /></mat-form-field>\n <mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.autoRows', 'Auto rows') }}</mat-label><mat-select [formControl]=\"deviceForms[device].controls.autoRows\"><mat-option value=\"\">{{ tx('editor.devices.orientationDefault', 'Default') }}</mat-option><mat-option value=\"fixed\">{{ tx('editor.layout.autoRowsFixed', 'Fixed height') }}</mat-option><mat-option value=\"content\">{{ tx('editor.layout.autoRowsContent', 'Fit content') }}</mat-option></mat-select></mat-form-field>\n </div>\n <details><summary>{{ tx('editor.devices.advanced', 'Advanced overrides') }}</summary><div class=\"grid\">\n <mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.rowUnit', 'Row unit') }}</mat-label><input matInput [formControl]=\"deviceForms[device].controls.rowUnit\" /></mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.devices.groupingOverridesJson', 'Grouping overrides (JSON)') }}</mat-label><textarea matInput rows=\"4\" [formControl]=\"deviceForms[device].controls.groupingOverrides\"></textarea></mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.devices.widgetOverridesJson', 'Widget overrides (JSON)') }}</mat-label><textarea matInput rows=\"4\" [formControl]=\"deviceForms[device].controls.widgetOverrides\"></textarea></mat-form-field>\n </div></details>\n @if (!stackedDevices.has(device)) { <div class=\"hint\">{{ tx('editor.preview.deviceCanvasHint', 'Existing canvas item overrides are preserved.') }}</div> }\n </div>\n }\n }\n </div>\n </mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.context', 'Context')\"><mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.context.json', 'Context (JSON)') }}</mat-label><textarea matInput rows=\"12\" [formControl]=\"contextControl\"></textarea></mat-form-field></mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.state', 'Page State')\"><mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.state.valuesJson', 'Primary values (JSON)') }}</mat-label><textarea matInput rows=\"14\" [formControl]=\"stateControl\"></textarea></mat-form-field></mat-tab>\n <mat-tab [label]=\"tx('editor.limits.tab', 'Widget limits')\">\n <section class=\"limits-panel\" data-testid=\"canvas-limits-editor\">\n @if (workspaceTab() === 3 && activeTab() === 'limits' && canvasLimits.targets.length) {\n <div class=\"selection-identity\" data-testid=\"canvas-limits-identity\">\n <mat-icon aria-hidden=\"true\">dashboard_customize</mat-icon>\n <strong>{{ limitsTargetLabel(limitsTargetControl.value) }}</strong>\n </div>\n }\n\n <p class=\"hint\" id=\"canvas-limits-help\">{{ tx('editor.limits.help', 'Limits use grid columns and rows, not pixels. Leave blank for defaults, or to inherit the base limits on a device.') }}</p>\n @if (canvasLimits.targets.length) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.target', 'Widget and layout') }}</mat-label>\n <mat-select [formControl]=\"limitsTargetControl\" data-testid=\"canvas-limits-target\">\n @for (target of canvasLimits.targets; track target.scope + ':' + target.key; let index = $index) {\n <mat-option [value]=\"index\">{{ limitsTargetLabel(index) }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <p class=\"hint\">{{ limitsGeometryLabel() }}</p>\n @if (canvasLimits.form.controls[limitsTargetControl.value]; as limitsForm) {\n <div class=\"limits-fields\" aria-describedby=\"canvas-limits-help\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.minColSpan', 'Minimum width (columns)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.minColSpan\" data-testid=\"canvas-limit-minColSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.minColSpan) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.maxColSpan', 'Maximum width (columns)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.maxColSpan\" data-testid=\"canvas-limit-maxColSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.maxColSpan) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.minRowSpan', 'Minimum height (rows)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.minRowSpan\" data-testid=\"canvas-limit-minRowSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.minRowSpan) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.maxRowSpan', 'Maximum height (rows)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.maxRowSpan\" data-testid=\"canvas-limit-maxRowSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.maxRowSpan) }}</mat-error>\n </mat-form-field>\n </div>\n @if (limitsUseContentHeight()) {\n <p class=\"limits-notice\"><mat-icon aria-hidden=\"true\">height</mat-icon><span>{{ tx('editor.limits.autoHeight', 'Choose natural height or an adjustable height with internal scrolling.') }}</span></p>\n <mat-button-toggle-group class=\"height-modes\" [formControl]=\"limitsForm.controls.heightMode\" [attr.aria-label]=\"tx('editor.height.mode', 'Widget height')\" data-testid=\"canvas-height-mode\">\n <mat-button-toggle value=\"inherit\">{{ tx('editor.height.inherit', 'Default') }}</mat-button-toggle>\n <mat-button-toggle value=\"auto\">{{ tx('editor.height.auto', 'Automatic') }}</mat-button-toggle>\n <mat-button-toggle value=\"fixed\">{{ tx('editor.height.fixed', 'Adjustable') }}</mat-button-toggle>\n </mat-button-toggle-group>\n @if (limitsForm.controls.heightMode.value === 'fixed') {\n <div class=\"limits-fields\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.value', 'Height (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"160\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.heightPx\" data-testid=\"canvas-height-px\">\n <mat-error>{{ limitFieldError(limitsForm.controls.heightPx) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.top', 'Top inset (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"0\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.offsetTopPx\" data-testid=\"canvas-height-top\">\n <mat-error>{{ limitFieldError(limitsForm.controls.offsetTopPx) }}</mat-error>\n </mat-form-field>\n </div>\n }\n <div class=\"limits-fields\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.min', 'Minimum height (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"160\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.minHeightPx\" data-testid=\"canvas-limit-minHeightPx\">\n <mat-error>{{ limitFieldError(limitsForm.controls.minHeightPx) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.max', 'Maximum height (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"160\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.maxHeightPx\" data-testid=\"canvas-limit-maxHeightPx\">\n <mat-error>{{ limitFieldError(limitsForm.controls.maxHeightPx) }}</mat-error>\n </mat-form-field>\n </div>\n }\n }\n @if (limitsSummaryError) { <p class=\"limits-error\" role=\"alert\">{{ limitsSummaryError }}</p> }\n } @else {\n <p>{{ tx('editor.limits.empty', 'Add widgets to the canvas to configure their limits.') }}</p>\n }\n </section>\n </mat-tab>\n </mat-tab-group></div>\n </mat-tab>\n </mat-tab-group>\n <details class=\"summary-details\">\n <summary>{{ tx('editor.summaryAria', 'Configuration summary') }}</summary>\n <div class=\"summary\">\n @if (!isHostedInSettingsPanel) {\n <span>{{ validationStateLabel }}</span>\n <span>{{ dirtyStateLabel }}</span>\n }\n <span>{{ selectedLayoutPresetLabel }}</span>\n <span>{{ selectedThemePresetLabel }}</span>\n <span>{{ canvasSummaryLabel }}</span>\n </div>\n </details>\n </div>\n", styles: [".page-inset-field{grid-column:1/-1;min-width:0}:host{display:block;min-width:0;max-width:100%}.height-modes{width:fit-content;max-width:100%;flex-wrap:wrap;margin-bottom:16px}.editor,.panel{display:grid;gap:16px;min-width:0;max-width:100%;color:var(--md-sys-color-on-surface, currentColor)}mat-tab-group,mat-form-field{min-width:0;max-width:100%}.head,.summary{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.summary{color:var(--md-sys-color-on-surface-variant, currentColor)}.selection-identity{display:flex;align-items:flex-start;gap:8px;padding-block:8px;background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface));overflow-wrap:anywhere}.selection-identity mat-icon{flex-shrink:0}:host ::ng-deep .editor-tabs>.mat-mdc-tab-header .mdc-tab{min-width:0;padding-inline:12px}:host ::ng-deep .editor-tabs>.mat-mdc-tab-header .mdc-tab__text-label{max-width:128px;white-space:normal;overflow-wrap:anywhere;text-align:center;letter-spacing:normal;line-height:1.25}.grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr))}@media(max-width:600px){.grid{grid-template-columns:minmax(0,1fr)}.head,.summary{align-items:stretch}.head>button{width:100%}}.card{display:grid;gap:12px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 18%, transparent));border-radius:8px;background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface-container, transparent));color:var(--md-sys-color-on-surface, currentColor)}.limits-panel{display:grid;gap:8px;min-width:0}.limits-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:4px 16px}.limits-fields mat-form-field{min-width:0}.limits-notice{display:flex;gap:8px;align-items:flex-start;color:var(--md-sys-color-on-surface-variant, currentColor)}.limits-notice mat-icon{flex-shrink:0}.limits-error{color:var(--md-sys-color-error, #b3261e)}.wide{grid-column:1/-1}.hint{color:var(--md-sys-color-on-surface-variant, currentColor);font-size:12px}.eyebrow{color:var(--md-sys-color-primary, currentColor);font-size:11px;font-weight:700;letter-spacing:0;text-transform:uppercase}:host ::ng-deep .mat-mdc-tab-body-content{padding-top:12px;box-sizing:border-box}:host{container-type:inline-size}.studio-panel{display:grid;gap:var(--p-space-4, 16px);padding-block:var(--p-space-5, 20px);min-width:0}.studio-heading h2{font-size:var(--p-font-size-lg, 20px);line-height:1.25;margin:var(--p-space-2, 8px) 0;font-weight:600}.studio-heading p{margin:0;color:var(--md-sys-color-on-surface-variant);line-height:1.5}.studio-panel h3{font-size:var(--p-font-size-md, 16px);margin:0}.studio-panel>button{justify-self:start}.studio-gallery{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-3, 12px)}.studio-choice{display:flex;flex-direction:column;gap:var(--p-space-2, 8px);min-width:0;padding:var(--p-space-3, 12px);text-align:start;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--p-radius-lg, 12px);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);font:inherit;cursor:pointer;transition:border-color var(--md-sys-motion-duration-short2, .1s),background-color var(--md-sys-motion-duration-short2, .1s)}.studio-choice:hover:not(:disabled){border-color:var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low)}.studio-choice.selected{border-color:var(--md-sys-color-primary);box-shadow:inset 0 0 0 1px var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low)}.studio-choice:focus-visible,.studio-panel summary:focus-visible{outline:3px solid var(--md-sys-color-primary);outline-offset:3px}.studio-choice:disabled{opacity:var(--p-disabled-opacity, .5);cursor:not-allowed}.choice-title{display:flex;align-items:center;justify-content:space-between;gap:var(--p-space-2, 8px);width:100%;font-weight:600;line-height:1.35}.choice-title mat-icon{color:var(--md-sys-color-primary);flex-shrink:0;font-size:20px;height:20px;width:20px}.choice-description{font-size:var(--p-font-size-sm, 13px);line-height:1.5;color:var(--md-sys-color-on-surface-variant)}.choice-meta{font-size:var(--p-font-size-xs, 12px);color:var(--md-sys-color-on-surface-variant);margin-top:auto;padding-top:var(--p-space-1, 4px)}.theme-sample{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));padding:var(--p-space-3, 12px);border-radius:var(--p-radius-md, 8px);min-height:128px;align-self:stretch;margin-bottom:var(--p-space-1, 4px)}.sample-metric,.sample-chart{background:var(--sample-surface);border-radius:var(--sample-radius);box-shadow:var(--sample-shadow);padding:var(--p-space-2, 8px)}.sample-metric b{display:block;font-size:var(--p-font-size-md, 16px)}.sample-metric i{display:block;height:3px;width:55%;margin-top:var(--p-space-1, 4px);background:var(--sample-accent)}.sample-chart{grid-column:1/-1;display:flex;height:40px;align-items:flex-end;gap:var(--p-space-2, 8px)}.sample-chart i{flex:1;background:var(--sample-accent);height:50%;border-radius:var(--p-radius-xs, 2px)}.sample-chart i:nth-child(2){height:85%}.sample-chart i:nth-child(3){height:65%}.sample-chart i:nth-child(4){height:100%}.sample-chart i:nth-child(5){height:75%}.shell-options,.arrangement-rules{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:var(--p-space-3, 12px)}.studio-panel summary{cursor:pointer;font-weight:500;line-height:1.5;padding-block:var(--p-space-2, 8px)}.shell-gallery{margin-top:var(--p-space-4, 16px)}.shell-sample{display:block;align-self:stretch;border:1px solid;overflow:hidden;margin:var(--p-space-2, 8px)}.shell-sample>span{display:flex;gap:var(--p-space-2, 8px);align-items:center;padding:var(--p-space-2, 8px);border-bottom:1px solid transparent}.shell-sample i{height:4px;width:60%;background:var(--md-sys-color-on-surface-variant);opacity:.4}.shell-sample>span:last-child{min-height:30px;flex-direction:column;align-items:flex-start}.shell-sample>span:last-child i:last-child{width:40%}.selection-line{margin:0;font-size:var(--p-font-size-sm, 13px);color:var(--md-sys-color-on-surface-variant)}.studio-actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--p-space-2, 8px)}.distribution-sample{display:grid;grid-template-rows:repeat(2,1fr);gap:var(--p-space-1, 4px);align-self:stretch;height:76px;padding:var(--p-space-2, 8px);background:var(--md-sys-color-surface-container);border-radius:var(--p-radius-md, 8px)}.distribution-sample i{display:grid;place-items:center;background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);border-radius:var(--p-radius-sm, 4px);font-size:var(--p-font-size-xs, 12px);font-style:normal}.distribution-sample i:nth-child(n+4){opacity:.65}.selected .distribution-sample i{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.arrangement-review{display:grid;gap:var(--p-space-3, 12px);padding-block:var(--p-space-4, 16px);border-top:2px solid var(--md-sys-color-primary)}.review-heading{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:var(--p-space-2, 8px)}.review-heading>span{font-size:var(--p-font-size-sm, 13px);color:var(--md-sys-color-primary)}.canvas-comparison{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-3, 12px)}.canvas-comparison figure{margin:0;min-width:0}.canvas-comparison figcaption{font-weight:500;margin-bottom:var(--p-space-2, 8px)}.canvas-map{display:grid;gap:2px;height:220px;padding:var(--p-space-2, 8px);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);border-radius:var(--p-radius-md, 8px)}.canvas-map span{display:grid;place-items:center;min-height:0;min-width:0;overflow:hidden;font-size:var(--p-font-size-xs, 12px);color:var(--md-sys-color-on-secondary-container);background:var(--md-sys-color-secondary-container);border-radius:var(--p-radius-xs, 2px)}.canvas-map .changed{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.widget-changes{padding-inline-start:var(--p-space-5, 20px);margin:0}.widget-changes li{padding-block:var(--p-space-2, 8px);overflow-wrap:anywhere}.widget-changes span{display:block;font-size:var(--p-font-size-sm, 13px);color:var(--md-sys-color-on-surface-variant)}.studio-status{border-inline-start:3px solid var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low);padding:var(--p-space-3, 12px);margin:0;line-height:1.5}@container (max-width:420px){.studio-gallery{grid-template-columns:minmax(0,1fr)}.canvas-map{height:180px}}@media(prefers-reduced-motion:reduce){.studio-choice{transition:none}}.arrangement-feedback:empty{display:none}.arrangement-feedback:focus{outline:none}.arrangement-feedback:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:3px}@container (min-width:620px){.arrangement-gallery{grid-template-columns:repeat(3,minmax(0,1fr))}}:host ::ng-deep .studio-tabs>.mat-mdc-tab-header{position:sticky;top:0;z-index:var(--praxis-layer-sticky-local, 100);background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface))}:host ::ng-deep .studio-tabs>.mat-mdc-tab-header .mdc-tab{flex:1 1 0;padding-inline:var(--p-space-1, 4px)}.arrangement-feedback{scroll-margin-block-start:var(--p-space-16, 64px)}\n"] }]
13741
14138
  }], ctorParameters: () => [], propDecorators: { page: [{
13742
14139
  type: Input
13743
14140
  }], layout: [{
@@ -13748,6 +14145,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
13748
14145
  type: Input
13749
14146
  }], pageChange: [{
13750
14147
  type: Output
14148
+ }], arrangementFeedback: [{
14149
+ type: ViewChild,
14150
+ args: ['arrangementFeedback']
13751
14151
  }] } });
13752
14152
 
13753
14153
  const DERIVED_OPERATORS = ['merge-objects', 'pick', 'omit'];
@@ -14424,6 +14824,7 @@ class PageConfigEditorComponent {
14424
14824
  lookupPresetLabel(entries, value, fallback) {
14425
14825
  return entries.find((entry) => entry.id === value)?.label || fallback;
14426
14826
  }
14827
+ get layoutPresetOptions() { return this.currentPageInput()?.layoutPresetOptions; }
14427
14828
  currentPageInput() {
14428
14829
  return this.parsePage(this.page()) || this.parsePage(this.injectedData?.page || null);
14429
14830
  }
@@ -14697,7 +15098,7 @@ class PageConfigEditorComponent {
14697
15098
  <mat-select [formControl]="layoutPresetControl">
14698
15099
  <mat-option value="">{{ tx('editor.layout.none', 'None') }}</mat-option>
14699
15100
  @for (preset of layoutPresetEntries; track preset.id) {
14700
- <mat-option [value]="preset.id">{{ preset.label }}</mat-option>
15101
+ <mat-option [value]="preset.id">{{ tx('presets.layout.' + preset.id, preset.label) }}</mat-option>
14701
15102
  }
14702
15103
  </mat-select>
14703
15104
  <button praxisIconButton="help_outline" size="dense" matSuffix class="help-icon-button" type="button" [matTooltip]="layoutPresetHint" [attr.aria-label]="layoutPresetHint" matTooltipPosition="above"></button>
@@ -14723,6 +15124,7 @@ class PageConfigEditorComponent {
14723
15124
  <button praxisIconButton="help_outline" size="dense" matSuffix class="help-icon-button" type="button" [matTooltip]="shellPresetHint" [attr.aria-label]="shellPresetHint" matTooltipPosition="above"></button>
14724
15125
  </mat-form-field>
14725
15126
  </div>
15127
+ <praxis-page-preset-comparison [layoutId]="layoutPresetControl.value" [layoutPresetOptions]="layoutPresetOptions" [themeId]="themePresetControl.value" (choose)="layoutPresetControl.setValue($event)" />
14726
15128
  </div>
14727
15129
  </mat-tab>
14728
15130
  <mat-tab [label]="tx('editor.tabs.grouping', 'Grouping')">
@@ -15208,11 +15610,11 @@ class PageConfigEditorComponent {
15208
15610
  </div>
15209
15611
  }
15210
15612
  </div>
15211
- `, isInline: true, styles: [".page-inset-field{grid-column:1 / -1;min-width:0}.page-config{display:grid;gap:12px;padding:16px;background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));border:1px solid var(--md-sys-color-outline-variant);border-radius:12px}.page-head{display:flex;align-items:flex-start;gap:12px}.page-head__copy{display:grid;gap:4px}.page-title{font-weight:600;font-size:18px;line-height:1.2}.page-subtitle{max-width:72ch;color:var(--md-sys-color-on-surface-variant);font-size:13px;line-height:1.45}.spacer{flex:1}.page-summary{display:flex;flex-wrap:wrap;gap:8px}.page-tabs{border-radius:16px;overflow:hidden;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 78%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 82%,transparent)}.page-tabs ::ng-deep .mdc-tab{min-width:120px}.page-tabs ::ng-deep .mdc-tab__text-label{font-size:13px;font-weight:600}.page-tab-panel{display:grid;gap:12px;padding:16px 4px 4px}.page-chip{display:inline-flex;align-items:center;gap:8px;min-height:36px;padding:0 12px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 88%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 82%,transparent);color:var(--md-sys-color-on-surface-variant);font-size:12px;font-weight:500}.page-chip mat-icon{width:18px;height:18px;font-size:18px}.page-chip--accent{border-color:color-mix(in srgb,var(--md-sys-color-primary) 35%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 30%,transparent)}.page-chip--warning{border-color:color-mix(in srgb,var(--md-sys-color-error) 35%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-error);background:color-mix(in srgb,var(--md-sys-color-error-container) 35%,transparent)}.page-identity{display:grid;gap:4px;padding:12px 14px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 82%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 70%,transparent);font-size:13px;color:var(--md-sys-color-on-surface-variant)}.page-section{font-weight:600;margin-top:8px}.page-section-help{margin-top:-4px;color:var(--md-sys-color-on-surface-variant);font-size:13px;line-height:1.45}.grid-form{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.state-grid{display:grid;gap:16px}.state-values,.state-group{padding:14px;border:1px solid var(--md-sys-color-outline-variant);border-radius:14px;background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-surface-container-low) 92%,transparent),transparent),var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface))}.state-group{display:grid;gap:12px}.state-group-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.state-group-title{font-weight:600}.state-group-help{color:var(--md-sys-color-on-surface-variant);font-size:13px}.state-list{display:grid;gap:12px}.state-card{display:grid;gap:12px;padding:12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 80%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container) 84%,transparent)}.state-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.state-card-title{font-weight:600}.state-card-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.span-2{grid-column:span 2}.span-3{grid-column:1 / -1}.state-empty{color:var(--md-sys-color-on-surface-variant);font-size:13px}.state-operator-hint{display:flex;align-items:center;gap:8px;min-height:48px;padding:0 12px;border-radius:12px;color:var(--md-sys-color-on-surface-variant);background:color-mix(in srgb,var(--md-sys-color-secondary-container) 35%,transparent)}.state-operator-hint mat-icon{color:var(--md-sys-color-secondary)}.page-actions{display:flex;justify-content:flex-end}.page-error{color:var(--md-sys-color-error);font-size:13px}.page-config .mat-mdc-form-field{width:100%;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.help-icon-button{margin-right:-4px}.mat-mdc-form-field-icon-suffix{align-self:center}@media(max-width:720px){.span-2,.span-3{grid-column:auto}.page-head{flex-direction:column}.state-group-head{align-items:stretch;flex-direction:column}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i8$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i8$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
15613
+ `, isInline: true, styles: [".page-inset-field{grid-column:1 / -1;min-width:0}.page-config{display:grid;gap:12px;padding:16px;background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));border:1px solid var(--md-sys-color-outline-variant);border-radius:12px}.page-head{display:flex;align-items:flex-start;gap:12px}.page-head__copy{display:grid;gap:4px}.page-title{font-weight:600;font-size:18px;line-height:1.2}.page-subtitle{max-width:72ch;color:var(--md-sys-color-on-surface-variant);font-size:13px;line-height:1.45}.spacer{flex:1}.page-summary{display:flex;flex-wrap:wrap;gap:8px}.page-tabs{border-radius:16px;overflow:hidden;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 78%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 82%,transparent)}.page-tabs ::ng-deep .mdc-tab{min-width:120px}.page-tabs ::ng-deep .mdc-tab__text-label{font-size:13px;font-weight:600}.page-tab-panel{display:grid;gap:12px;padding:16px 4px 4px}.page-chip{display:inline-flex;align-items:center;gap:8px;min-height:36px;padding:0 12px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 88%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 82%,transparent);color:var(--md-sys-color-on-surface-variant);font-size:12px;font-weight:500}.page-chip mat-icon{width:18px;height:18px;font-size:18px}.page-chip--accent{border-color:color-mix(in srgb,var(--md-sys-color-primary) 35%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 30%,transparent)}.page-chip--warning{border-color:color-mix(in srgb,var(--md-sys-color-error) 35%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-error);background:color-mix(in srgb,var(--md-sys-color-error-container) 35%,transparent)}.page-identity{display:grid;gap:4px;padding:12px 14px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 82%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 70%,transparent);font-size:13px;color:var(--md-sys-color-on-surface-variant)}.page-section{font-weight:600;margin-top:8px}.page-section-help{margin-top:-4px;color:var(--md-sys-color-on-surface-variant);font-size:13px;line-height:1.45}.grid-form{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.state-grid{display:grid;gap:16px}.state-values,.state-group{padding:14px;border:1px solid var(--md-sys-color-outline-variant);border-radius:14px;background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-surface-container-low) 92%,transparent),transparent),var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface))}.state-group{display:grid;gap:12px}.state-group-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.state-group-title{font-weight:600}.state-group-help{color:var(--md-sys-color-on-surface-variant);font-size:13px}.state-list{display:grid;gap:12px}.state-card{display:grid;gap:12px;padding:12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 80%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container) 84%,transparent)}.state-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.state-card-title{font-weight:600}.state-card-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.span-2{grid-column:span 2}.span-3{grid-column:1 / -1}.state-empty{color:var(--md-sys-color-on-surface-variant);font-size:13px}.state-operator-hint{display:flex;align-items:center;gap:8px;min-height:48px;padding:0 12px;border-radius:12px;color:var(--md-sys-color-on-surface-variant);background:color-mix(in srgb,var(--md-sys-color-secondary-container) 35%,transparent)}.state-operator-hint mat-icon{color:var(--md-sys-color-secondary)}.page-actions{display:flex;justify-content:flex-end}.page-error{color:var(--md-sys-color-error);font-size:13px}.page-config .mat-mdc-form-field{width:100%;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.help-icon-button{margin-right:-4px}.mat-mdc-form-field-icon-suffix{align-self:center}@media(max-width:720px){.span-2,.span-3{grid-column:auto}.page-head{flex-direction:column}.state-group-head{align-items:stretch;flex-direction:column}}\n"], dependencies: [{ kind: "component", type: PagePresetComparisonComponent, selector: "praxis-page-preset-comparison", inputs: ["expanded", "layoutId", "themeId", "layoutPresetOptions", "canvas"], outputs: ["choose"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i9.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i9.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i8.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i8.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
15212
15614
  }
15213
15615
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PageConfigEditorComponent, decorators: [{
15214
15616
  type: Component,
15215
- args: [{ selector: 'praxis-page-config-editor', standalone: true, imports: [
15617
+ args: [{ selector: 'praxis-page-config-editor', standalone: true, imports: [PagePresetComparisonComponent,
15216
15618
  ReactiveFormsModule,
15217
15619
  MatButtonModule,
15218
15620
  MatFormFieldModule,
@@ -15295,7 +15697,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
15295
15697
  <mat-select [formControl]="layoutPresetControl">
15296
15698
  <mat-option value="">{{ tx('editor.layout.none', 'None') }}</mat-option>
15297
15699
  @for (preset of layoutPresetEntries; track preset.id) {
15298
- <mat-option [value]="preset.id">{{ preset.label }}</mat-option>
15700
+ <mat-option [value]="preset.id">{{ tx('presets.layout.' + preset.id, preset.label) }}</mat-option>
15299
15701
  }
15300
15702
  </mat-select>
15301
15703
  <button praxisIconButton="help_outline" size="dense" matSuffix class="help-icon-button" type="button" [matTooltip]="layoutPresetHint" [attr.aria-label]="layoutPresetHint" matTooltipPosition="above"></button>
@@ -15321,6 +15723,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
15321
15723
  <button praxisIconButton="help_outline" size="dense" matSuffix class="help-icon-button" type="button" [matTooltip]="shellPresetHint" [attr.aria-label]="shellPresetHint" matTooltipPosition="above"></button>
15322
15724
  </mat-form-field>
15323
15725
  </div>
15726
+ <praxis-page-preset-comparison [layoutId]="layoutPresetControl.value" [layoutPresetOptions]="layoutPresetOptions" [themeId]="themePresetControl.value" (choose)="layoutPresetControl.setValue($event)" />
15324
15727
  </div>
15325
15728
  </mat-tab>
15326
15729
  <mat-tab [label]="tx('editor.tabs.grouping', 'Grouping')">
@@ -17013,6 +17416,18 @@ const PRAXIS_PAGE_BUILDER_AUTHORING_MANIFEST = {
17013
17416
  targetKind: 'widgetShell',
17014
17417
  target: { kind: 'widgetShell', resolver: 'widget-shell-by-widget-key', ambiguityPolicy: 'fail', required: true },
17015
17418
  inputSchema: { type: 'object', required: ['widgetKey'], properties: { widgetKey: { type: 'string' }, shell: { type: 'object', properties: {
17419
+ appearance: { type: 'object', properties: {
17420
+ card: { type: 'object', properties: {
17421
+ background: { type: 'string' }, borderColor: { type: 'string' }, borderWidth: { type: 'string', description: 'CSS border width; accepts per-side values.' }, borderStyle: { type: 'string', description: 'CSS border style: solid, dashed, dotted, double, none; accepts per-side values.' }, borderRadius: { type: 'string' }, shadow: { type: 'string' },
17422
+ backdropFilter: { type: 'string', description: 'CSS backdrop effect for a translucent card, e.g. blur(16px) saturate(125%). none disables it.' },
17423
+ fallbackBackground: { type: 'string', description: 'Opaque background for reduced transparency, unsupported blur and fullscreen/expanded modes.' },
17424
+ } },
17425
+ header: { type: 'object', properties: {
17426
+ background: { type: 'string' }, borderColor: { type: 'string' },
17427
+ borderWidth: { type: 'string', description: 'CSS width of the bottom header divider.' }, borderStyle: { type: 'string', description: 'CSS style of the bottom header divider.' },
17428
+ titleColor: { type: 'string' }, subtitleColor: { type: 'string' }, iconColor: { type: 'string' },
17429
+ } },
17430
+ } },
17016
17431
  avatar: { type: 'object', additionalProperties: false, properties: {
17017
17432
  imageSrc: { type: 'string', description: 'Photo URL or shell template, including ${transient.employee.avatarUrl}.' },
17018
17433
  name: { type: 'string', description: 'Accessible name and initials fallback; supports shell templates.' },