/* ---------- @font-face: OpenSans (10 TTF variants from KenergyUI/Kenergy/Assets/Fonts/) ---------- */
@font-face { font-family: "Open Sans"; src: url('fonts/OpenSans-Light.ttf') format('truetype'); font-weight: 300; font-style: normal; font-display: swap; }
@font-face { font-family: "Open Sans"; src: url('fonts/OpenSans-LightItalic.ttf') format('truetype'); font-weight: 300; font-style: italic; font-display: swap; }
@font-face { font-family: "Open Sans"; src: url('fonts/OpenSans-Regular.ttf') format('truetype'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Open Sans"; src: url('fonts/OpenSans-Italic.ttf') format('truetype'); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: "Open Sans"; src: url('fonts/OpenSans-Semibold.ttf') format('truetype'); font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: "Open Sans"; src: url('fonts/OpenSans-SemiboldItalic.ttf') format('truetype'); font-weight: 600; font-style: italic; font-display: swap; }
@font-face { font-family: "Open Sans"; src: url('fonts/OpenSans-Bold.ttf') format('truetype'); font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: "Open Sans"; src: url('fonts/OpenSans-BoldItalic.ttf') format('truetype'); font-weight: 700; font-style: italic; font-display: swap; }
@font-face { font-family: "Open Sans"; src: url('fonts/OpenSans-ExtraBold.ttf') format('truetype'); font-weight: 800; font-style: normal; font-display: swap; }
@font-face { font-family: "Open Sans"; src: url('fonts/OpenSans-ExtraBoldItalic.ttf') format('truetype'); font-weight: 800; font-style: italic; font-display: swap; }

/* Kenergy brand palette from KenergyUI/Kenergy/App.axaml */
:root {
    --kiq-basic:                    #016FE6;
    --kiq-basic-light:              #019EFF;
    --kiq-basic-dark:               #015CBE;
    --kiq-basic-gray:               #969696;
    --kiq-basic-white:               #FFFFFF;
    --kiq-basic-background-light:   #CCE2FA;
    --kiq-basic-background-dark:    #7FB7F2;
    --kiq-warning:                  #FBCA16;
    --kiq-warning-background-light: #FFF5D8;
    --kiq-danger:                   #F62447;
    --kiq-danger-light:             #FF254A;
    --kiq-danger-dark:              #DB203F;
    --kiq-danger-background-light:  #F7DCDC;
    --kiq-danger-background-dark:   #F9C8D0;
    --kiq-selected:                 #E3F3FF;
    --kiq-pointerover:              #F7F7F7;

    /* Secondary palette — the desktop's "secondary" button / chip / input chrome
       inlines these hex codes across App.axaml (Button.kiq_secondary, ImageButton,
       ImageToggleButton, ComboBox.period). Tokenizing them here so the web has the
       same single source of truth the desktop assumes via repetition. */
    --kiq-secondary-bg:             #F2F6F9;
    --kiq-secondary-border:         #CDD3D9;
    --kiq-secondary-fg:             #4F5766;
    /* TabItem foreground, ComboBox chevron fill, and progress-spinner glyph —
       desktop's ubiquitous mid-gray for control chrome (§4.1 "tab text default
       595959", "spinner glyph 595959"). Body text uses --kiq-body-color
       (#4F5766) instead; the two were conflated before but the §4.1 spec
       treats them as distinct semantics. */
    --kiq-text-primary:             #595959;
    --kiq-tab-text-default:         #595959;
    /* §4.1 body text color — slightly darker than --kiq-text-primary; used by
       the global <body> rule and any surface that explicitly opts in. */
    --kiq-body-color:               #4F5766;

    /* §4.1 additional fixed values from App.axaml — locked, not rounded. */
    --kiq-tab-hover-border:         #BEBEBE;
    --kiq-disabled-bg:              #F0F0F0;
    --kiq-disabled-fg:              #D2D2D2;
    --kiq-card-shadow-color:        #D2D2D2;
    --kiq-datagrid-row-tint:        #F7F7F7;
    --kiq-chart-axis-line:          #DCE1E6;
    --kiq-tracker-popup-bg:         #99000000;
    --kiq-zoom-accept-fill:         #33902537;
    --kiq-zoom-reject-fill:         #332F6931;
    --kiq-zoom-dash-stroke:         #000000;
    --kiq-zoom-dash-array:          3, 1;
    --kiq-error-text:               #F62447;
    --kiq-building-import-removed:  #B00020;
    --kiq-building-import-added:    #1B5E20;
    --kiq-building-import-badge-bg: #EEF2FF;

    /* Non-palette desktop-sourced utility tokens (used by App.axaml-derived styles) */
    --kiq-hover:                 #F7F7F7;
    /* Single source of truth for the app-wide surface background. Every main
       structural surface (shell, header, sidebar, body, content, statusbar)
       routes its background through this token so the whole app stays one
       consistent color and can be re-themed from one place. */
    --kiq-app-bg:                var(--kiq-basic-white);
    --kiq-panel-bg:              #F0F0F0;
    --kiq-tab-header-bg:         #F6F6F6;
    --kiq-stepper-bg:            #E5E5E5;
    /* §4.1 spinner aliases — surface E5E5E5, glyph 595959. Routed through the
       existing tokens so a future palette swap stays single-source. */
    --kiq-spinner-surface:       var(--kiq-stepper-bg);
    --kiq-spinner-glyph:         var(--kiq-text-primary);
    --kiq-focus:                 #016FE6;

    /* Material 3 elevation tokens (key umbra + ambient penumbra) — used by
       surfaces that need a resting shadow. Values match the M3 spec; consume
       via box-shadow: var(--m3-elevation-N). Shadow color routes through
       --md-sys-color-shadow per the M3 design-token system so it can be
       themed without rewriting every elevation token. */
    --md-sys-color-shadow: 0, 0, 0;
    --m3-elevation-0: none;
    --m3-elevation-1: 0px 1px 2px 0px rgba(var(--md-sys-color-shadow), 0.28),
                      0px 2px 6px 0px rgba(var(--md-sys-color-shadow), 0.16);
    --m3-elevation-2: 0px 2px 4px 0px rgba(var(--md-sys-color-shadow), 0.28),
                      0px 4px 12px 0px rgba(var(--md-sys-color-shadow), 0.20);
    --m3-elevation-3: 0px 4px 8px 0px rgba(var(--md-sys-color-shadow), 0.24),
                      0px 10px 24px 0px rgba(var(--md-sys-color-shadow), 0.22);
    --m3-elevation-4: 0px 6px 12px 0px rgba(var(--md-sys-color-shadow), 0.26),
                      0px 14px 32px 0px rgba(var(--md-sys-color-shadow), 0.24);
    --m3-elevation-5: 0px 8px 16px 0px rgba(var(--md-sys-color-shadow), 0.28),
                      0px 20px 40px 0px rgba(var(--md-sys-color-shadow), 0.26);

    /* ---------- Design tokens (App.axaml §4) ----------
       Single source of truth for typography, spacing, layout, and animation.
       Consume via var(--kiq-*) rather than re-hardcoding. */

    /* §4.2 Typography (12 px default; 16 px Bold #4F5766 card/dialog titles;
       10 weights of Open Sans loaded via @font-face above). §4 spec is
       "no system substitution" — the @font-face TTFs in wwwroot/fonts are
       the only allowed source, so this token names "Open Sans" alone with
       no system fallback chain. */
    --kiq-font-family:           "Open Sans";
    --kiq-font-size:             12px;
    --kiq-title-font-size:       16px;
    --kiq-title-font-weight:     700;
    --kiq-title-color:           #4F5766;
    /* Section / parameter-row label = 12 px Bold, default fg #000000.
       Font-size matches --kiq-font-size but is named separately so callers
       opting in via `.k-param-label` route through §4 tokens rather than
       re-stating "12 px Bold" inline. */
    --kiq-section-label-font-size: 12px;
    --kiq-section-label-weight:  700;
    --kiq-section-label-color:   #000000;
    /* Tracker tooltip = 10 px white on #99000000. */
    --kiq-tracker-font-size:     10px;
    --kiq-tracker-font-color:    #FFFFFF;
    /* Error text = 10 px SemiBold #F62447 (prefixed with U+1F7AB Heavy Ballot X). */
    --kiq-error-font-size:       10px;
    --kiq-error-font-weight:     600;

    /* §4.3 Card spacing (margin 0 0 0 24, padding 18 14, radius 14,
       shadow 0 0 10 1 #D2D2D2, border thickness 0). */
    --kiq-card-margin:           0 0 24px;
    --kiq-card-padding:          14px 18px;
    --kiq-card-radius:           14px;
    --kiq-card-shadow:           0 0 10px 1px #D2D2D2;
    --kiq-card-border-thickness: 0;
    /* Card title-row → content gap 14 px; content → button row gap 28 px (sometimes 18 px). */
    --kiq-card-title-gap:        14px;
    --kiq-card-button-gap:       28px;
    --kiq-card-button-gap-tight: 18px;
    --kiq-card-listbox-margin:   0 14px 0 0;

    /* §4.3 Controls (button gap 11 px, button height ~30 px, min-width 96 px,
       input height 28 px). */
    --kiq-button-gap:            11px;
    --kiq-button-height:         30px;
    --kiq-button-min-width:      96px;
    --kiq-input-height:          28px;

    /* §4.3 Parameter row primitives (240 px fixed width, 0 0 28 0 padding,
       inner editor 176 px, NumericUpDownUnits 128 px, 2 px stripe with 16 px
       gap to label). */
    --kiq-param-row-width:       240px;
    --kiq-param-row-padding:     0 28px 0 0;
    --kiq-param-editor-width:    176px;
    --kiq-param-numeric-width:   128px;
    --kiq-param-stripe-width:    2px;
    --kiq-param-stripe-margin:   0 16px 0 0;

    /* §4.3 Layout (tree pane 360 px, splitter 1 px). */
    --kiq-tree-pane-width:       360px;
    --kiq-splitter-width:        1px;

    /* §4.3 Chart (canvas 250 px tall, plot margin 14 0 14 25). */
    --kiq-chart-canvas-height:   250px;
    --kiq-chart-plot-margin:     14px 0 14px 25px;

    /* §4.3 Tracker popup (MinWidth 180, MaxHeight 500, CornerRadius 2). */
    --kiq-tracker-min-width:     180px;
    --kiq-tracker-max-height:    500px;
    --kiq-tracker-radius:        2px;

    /* §4.3 Dialog widths (parameter editor 320, confirm 420, progress 420,
       loading 300, building-import 860×560 minimum). */
    --kiq-dialog-small-width:        320px;
    --kiq-dialog-confirm-min-width:  420px;
    --kiq-dialog-progress-width:     420px;
    --kiq-dialog-loading-width:      300px;
    --kiq-dialog-import-min-width:   860px;
    --kiq-dialog-import-min-height:  560px;

    /* §4.3 Tab item (MinHeight 36, MinWidth 96; selected = 3 px top border #006FE6). */
    --kiq-tab-min-height:        36px;
    --kiq-tab-min-width:         96px;
    --kiq-tab-indicator-width:   3px;
    --kiq-tab-indicator-color:   #016FE6;

    /* §4.4 Animation tokens.
       - BrushTransition 0.1 s = parameter changed stripe fill swap.
       - AutoScrollListBox pulse = 1 s infinite loop, LightGray ↔ Orange border.
       - Notification flash = ~300 ms decay from #009EFF back to #E9E9E9. */
    --kiq-brush-transition:      0.1s;
    --kiq-autoscroll-pulse:      1s;
    --kiq-autoscroll-pulse-from: lightgray;
    --kiq-autoscroll-pulse-to:   orange;
    --kiq-flash-decay:           300ms;
    /* §4.4 progress spinner = 4-frame loop at 250 ms per frame
       (processing_1..4_x32.png), so the full cycle is 1 s. Consumers route
       both their `steps(4)` keyframes timing and any per-frame derived sites
       through these tokens. */
    --kiq-spinner-frame-duration: 250ms;
    --kiq-spinner-cycle:          1s;

    /* ParameterTextColor palette (Parameters/ParameterTextColor.cs).
       Canonical hexes from the desktop enum — single source of truth for every
       parameter text-color modifier (.k-param-value.k-*, .k-text-*, .k-mp-*). */
    --kiq-param-text-default:    #000000;
    --kiq-param-text-gray:       LightGray;
    --kiq-param-text-blue:       #006FE6;
    --kiq-param-text-green:      #65C128;
    --kiq-param-text-orange:     #FFC800;
    --kiq-param-text-red:        #F62447;
    /* Parameter row dirty / clean stripe + notification flash from §8.1. */
    --kiq-param-stripe-clean:    #E9E9E9;
    --kiq-param-stripe-dirty:    #FFB900;
    --kiq-param-stripe-flash:    #009EFF;

    /* Radzen theme token overrides — remap to Kenergy brand. */
    --rz-primary:                var(--kiq-basic);
    --rz-primary-light:          var(--kiq-basic-light);
    --rz-primary-dark:           var(--kiq-basic-dark);
    --rz-primary-lighter:        var(--kiq-basic-background-light);
    --rz-on-primary:             #ffffff;
    --rz-secondary:              var(--kiq-basic-gray);
    --rz-warning:                var(--kiq-warning);
    --rz-danger:                 var(--kiq-danger);
    --rz-base-background-color:  var(--kiq-basic-white);
    --rz-text-color:             #595959;
    --rz-text-secondary-color:   var(--kiq-basic-gray);
    --rz-header-height:          48px;
    --rz-sidebar-width:          var(--kiq-tree-pane-width);

    /* Force Open Sans across every Radzen 10.3.0 component. */
    --rz-text-font-family:       var(--kiq-font-family);
}

html, body { background: var(--kiq-app-bg); }
body {
    font-family: var(--kiq-font-family);
    font-size: var(--kiq-font-size);   /* mirrors desktop 12pt default */
    color: var(--kiq-body-color);      /* §4.1: body text #4F5766 */
}

/* ---------- Layout: white header + thin gray separator + 360px sidebar ---------- */
.rz-header {
    background: var(--kiq-app-bg) !important;
    color: #1c1c1c !important;
    border-bottom: 1px solid lightgray !important;
    box-shadow: none !important;
    min-height: 48px;
    height: 48px;
    padding: 0 12px !important;
}
.rz-header a, .rz-header .rz-button-text { color: #1c1c1c !important; }

.rz-sidebar {
    background: var(--kiq-app-bg) !important;
    border-right: 1px solid lightgray !important;
    padding: 0 !important;
}

.rz-body { background: var(--kiq-app-bg); }

/* ---------- Tree: exact replica of App.axaml TreeViewItem styles ----------
   Targets Radzen 10.3.0 DOM: .rz-treenode (item), .rz-treenode-content (row),
   .rz-tree-toggler (chevron <i> with .rzi-caret-right when collapsed and
   .rzi-caret-down when expanded), .rz-treenode-leaf (no-children marker). */
.rz-tree, .rz-tree.rz-tree-state-default {
    padding: 0;
    border: none;
    background: transparent;
    font-family: inherit;
    font-size: 12px;
}
.rz-tree ul, .rz-tree-container, .rz-treenode-children { list-style: none; margin: 0; }

/* Single node row */
.rz-tree .rz-treenode-content {
    padding: 2px 12px 2px 6px !important;
    min-height: 28px !important;
    display: flex !important;
    align-items: center;
    font-size: 12px;
    line-height: 16px;
    color: #1A1A1A;
    border: none !important;
    border-radius: 0 !important;
    cursor: pointer;
}

/* Hover — Kenergy AI design kit (.k-tree-row:hover #F4F8FC). */
.rz-tree .rz-treenode-content:not(.rz-treenode-content-selected):hover,
.rz-tree .rz-treenode-content:not(.rz-treenode-content-selected):hover .rz-treenode-label {
    background: #F4F8FC !important;
}

/* Selected — Kenergy AI design kit (.k-tree-row.selected #E6F2FB). */
.rz-tree .rz-treenode-content-selected,
.rz-tree .rz-treenode-content-selected .rz-treenode-label,
.rz-tree .rz-treenode-content-selected:hover,
.rz-tree .rz-treenode-content-selected:hover .rz-treenode-label {
    background: #E6F2FB !important;
    color: #1A1A1A !important;
}

/* Expand/collapse chevron — replace Material caret with desktop +/- PNGs.
   Radzen sets .rzi-caret-right on the toggler when collapsed and
   .rzi-caret-down when expanded. */
.rz-tree .rz-tree-toggler {
    width: 16px !important;
    height: 16px !important;
    padding: 0 !important;
    background: transparent !important;
    border: none !important;
    line-height: 16px !important;
    color: transparent !important;
    text-indent: -9999px;
    flex-shrink: 0;
}
.rz-tree .rz-tree-toggler::before {
    content: '';
    display: block;
    width: 16px;
    height: 16px;
    /* KEN-420 §5.3: App.axaml sets Margin="0 0 -4 1" (LTRB) on
       PART_ExpandCollapseChevron — translate to CSS top/right/bottom/left so
       the toggler pulls 4 px toward the per-node icon and drops 1 px to align
       its baseline with the label glyph. */
    margin: 0 -4px 1px 0 !important;
    background-image: url('icons/bullet_toggle_plus_x16.png');
    background-repeat: no-repeat;
    background-position: center;
    background-size: 16px 16px;
    text-indent: 0;
}
.rz-tree .rz-tree-toggler.rzi-caret-down::before {
    background-image: url('icons/bullet_toggle_minus_x16.png');
}

/* Leaf-node toggler space (kept 16px so labels line up) */
.rz-tree .rz-treenode-leaf .rz-tree-toggler { visibility: hidden; }

/* Plan-spec nav-tree-item selectors (mirror desktop TreeViewItem hover/selected). */
.nav-tree-item:hover { background: var(--kiq-pointerover); }
.nav-tree-item.selected { background: var(--kiq-selected); }

/* Custom row layout used by NavTree templates. The row fills the full
   treenode-label width so long names truncate cleanly rather than
   expanding the row past the sidebar splitter. */
.rz-tree .rz-treenode-label { flex: 1 1 auto; min-width: 0; }
/* Inter-element spacing mirrors the desktop tree templates (MainView.axaml):
   leading icon -> name is 8px (Image Margin="0 0 8 0"), the item count is 4px
   (TextBlock Margin="4 0 0 0"), and every other inline element (DeviceId/meta
   and trailing badge icons) uses the desktop 6px (Margin="6 ...0 0"). */
.ktree-row { display: flex; width: 100%; min-width: 0; align-items: center; padding: 1px 2px; }
.ktree-row > * { margin-left: 6px; }
.ktree-row > *:first-child { margin-left: 0; }
.ktree-row > .ktree-icon + .ktree-name { margin-left: 8px; }
.ktree-row > .ktree-count { margin-left: 4px; }
.ktree-row .ktree-icon { width: 16px; height: 16px; image-rendering: -webkit-optimize-contrast; flex-shrink: 0; }
.ktree-row .ktree-name  { color: #1A1A1A; font-size: 12px; min-width: 0; flex: 0 1 auto; }
.ktree-row .ktree-count { color: #888; font-size: 12px; flex-shrink: 0; }
.ktree-row .ktree-meta  { color: #888; font-size: 12px; flex-shrink: 0; }
.ktree-row.ktree-inactive, .ktree-row .ktree-inactive, span.ktree-row.ktree-inactive { opacity: .4; }
.ktree-row .ktree-err   { color: var(--kiq-danger); font-size: 12px; flex-shrink: 0; }
/* Status dot — Kenergy AI design kit (.k-tree-row .dot): 8px trailing circle,
   neutral grey by default, recolored by ok/warn/danger tone classes. */
.ktree-row .ktree-dot { width: 8px; height: 8px; border-radius: 50%; background: #B6B6B6; flex-shrink: 0; display: inline-block; }
.ktree-row .ktree-dot.ok     { background: #6BC36A; }
.ktree-row .ktree-dot.warn   { background: #FBCA16; }
.ktree-row .ktree-dot.danger { background: #F62447; }
.ktree-row .ktree-signal { width: 16px; height: 16px; flex-shrink: 0; }
.ktree-row .ktree-logo   { object-fit: contain; }

/* ---------- Buttons: exact replicas of c:ImageButton styles from App.axaml ---------- */
.rz-button, .rz-button.rz-button-md {
    font-family: inherit;
    font-size: 12px !important;
    text-transform: none !important;
    box-shadow: none !important;
    border-radius: 4px !important;
    min-height: 28px;
    padding: 4px 16px !important;
    /* App.axaml buttons set FontSize=12 but no FontWeight → Avalonia default
       Normal (400). Match it rather than the prior 500. */
    font-weight: 400;
}

/* kiq_primary */
.rz-button.rz-primary {
    background: var(--kiq-basic) !important;
    border: 1px solid var(--kiq-basic) !important;
    color: #fff !important;
}
.rz-button.rz-primary:hover { background: var(--kiq-basic-light) !important; border-color: var(--kiq-basic-light) !important; }
.rz-button.rz-primary:active { background: var(--kiq-basic-dark) !important; border-color: var(--kiq-basic-dark) !important; }
.rz-button.rz-primary:disabled { background: #F0F0F0 !important; border-color: #F0F0F0 !important; color: #D2D2D2 !important; }

/* kiq_secondary (light gray with brand-blue hover) */
.rz-button.rz-secondary,
.rz-button.rz-button-secondary {
    background: #F2F6F9 !important;
    border: 1px solid #CDD3D9 !important;
    color: #4F5766 !important;
}
.rz-button.rz-secondary:hover,
.rz-button.rz-button-secondary:hover {
    border-color: var(--kiq-basic-light) !important;
    color: var(--kiq-basic-light) !important;
    background: #F2F6F9 !important;
}
.rz-button.rz-secondary:active { background: var(--kiq-basic-background-light) !important; border-color: var(--kiq-basic) !important; color: var(--kiq-basic) !important; }

/* kiq_danger / kiq_secondary_danger */
.rz-button.rz-danger {
    background: var(--kiq-danger) !important;
    border: 1px solid var(--kiq-danger) !important;
    color: #fff !important;
}
.rz-button.rz-danger:hover { background: var(--kiq-danger-light) !important; border-color: var(--kiq-danger-light) !important; }
.rz-button.rz-danger:active { background: var(--kiq-danger-dark) !important; border-color: var(--kiq-danger-dark) !important; }

.rz-button.rz-warning {
    background: var(--kiq-warning) !important;
    border: 1px solid var(--kiq-warning) !important;
    color: #595959 !important;
}

/* ---------- ImageButton (c:ImageButton replica) ---------- */
/* Mirrors App.axaml line 957 c:ImageButton: MinWidth=96, Margin="0 0 11 0"
   (the 11 px is the inter-button right-margin, not the icon→text gap). */
.kiq-image-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    background: #ffffff;
    border: 1px solid #CDD3D9;
    color: #4F5766;
    border-radius: 4px;
    min-width: 96px;
    height: 30px;
    margin-right: 11px;
    padding: 0 14px;
    font-size: 12px;
    font-weight: 400;
    cursor: pointer;
    user-select: none;
    text-decoration: none;
    transition: border-color .08s ease, color .08s ease, background .08s ease;
}
.kiq-image-button .kiq-icon { width: 16px; height: 16px; }
.kiq-image-button:hover { border-color: var(--kiq-basic-light); color: var(--kiq-basic-light); }
.kiq-image-button:active { background: var(--kiq-basic-background-light); border-color: var(--kiq-basic); color: var(--kiq-basic); }
.kiq-image-button:disabled { background: #fff; border-color: #D2D2D2; color: #D2D2D2; cursor: not-allowed; }
.kiq-image-button:disabled .kiq-icon { filter: var(--kiq-disabled-icon-filter); }
.kiq-image-button.kiq-primary {
    background: var(--kiq-basic);
    border-color: var(--kiq-basic);
    color: #fff;
}
.kiq-image-button.kiq-primary:hover { background: var(--kiq-basic-light); border-color: var(--kiq-basic-light); color: #fff; }
.kiq-image-button.kiq-primary:active { background: var(--kiq-basic-dark); border-color: var(--kiq-basic-dark); color: #fff; }
.kiq-image-button.kiq-danger {
    background: #F2F6F9;
    border-color: #CDD3D9;
    color: #4F5766;
}
.kiq-image-button.kiq-danger:hover { border-color: var(--kiq-danger-light); color: var(--kiq-danger-light); }
/* App.axaml c:ImageButton.kiq_secondary_danger :pressed fills kiq_basic_background_light
   (#CCE2FA), keeping only the border+text danger-red. */
.kiq-image-button.kiq-danger:active { background: var(--kiq-basic-background-light); border-color: var(--kiq-danger); color: var(--kiq-danger); }

/* ---------- Tabs (.k-tabs) — single source of truth.
   Applied via class="k-tabs" on every <RadzenTabs> in this app.
   Mirrors App.axaml TabControl with a bottom-underline indicator. ---------- */
.k-tabs.rz-tabview,
.k-tabs .rz-tabview {
    background: transparent !important;
    box-shadow: none !important;
    border: none !important;
    border-radius: 0 !important;
}

.k-tabs .rz-tabview-nav {
    background: #F6F6F6 !important;
    border: none !important;
    border-bottom: 1px solid #E0E0E0 !important;
    /* Side gutter insets the tab row from the panel edge, matching the
       live-screenshot recreation in the design handoff. */
    padding: 0 20px !important;
    gap: 0 !important;
    border-radius: 0 !important;
}

.k-tabs .rz-tabview-nav li,
.k-tabs .rz-tabview-nav li.rz-state-default {
    background: transparent !important;
    border: none !important;
    border-top: 3px solid transparent !important;
    border-left: 1px solid transparent !important;
    border-right: 1px solid transparent !important;
    margin: 0 !important;
    border-radius: 0 !important;
    min-height: 36px;
    min-width: 96px;
}

.k-tabs .rz-tabview-nav li .rz-tabview-title,
.k-tabs .rz-tabview-nav li a,
.k-tabs .rz-tabview-nav li button {
    font-family: var(--kiq-font-family) !important;
    font-size: 12px !important;
    font-weight: 400 !important;
    color: #595959 !important;
    text-transform: none !important;
    min-height: 36px;
    min-width: 96px;
    padding: 0 16px !important;
    border: none !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    background: transparent !important;
}

.rz-tabview-nav li button {
    padding-block: 0 !important;
}

.k-tabs .rz-tabview-nav li:hover:not(.rz-state-active):not(.rz-tabview-selected) {
    border-top-color:   var(--kiq-tab-hover-border) !important;
    border-left-color:  var(--kiq-tab-hover-border) !important;
    border-right-color: var(--kiq-tab-hover-border) !important;
}

.k-tabs .rz-tabview-nav li.rz-state-active,
.k-tabs .rz-tabview-nav li.rz-tabview-selected {
    background: #FFFFFF !important;
    /* Design bundle components-tabs.html: selected tab border-top-color = --kiq_basic
       (#016FE6, the brand blue). Routed through the indicator token. */
    border-top: 3px solid var(--kiq-tab-indicator-color) !important;
}

.k-tabs .rz-tabview-nav li.rz-state-active a,
.k-tabs .rz-tabview-nav li.rz-state-active button,
.k-tabs .rz-tabview-nav li.rz-state-active .rz-tabview-title,
.k-tabs .rz-tabview-nav li.rz-tabview-selected a,
.k-tabs .rz-tabview-nav li.rz-tabview-selected button,
.k-tabs .rz-tabview-nav li.rz-tabview-selected .rz-tabview-title {
    /* selected TabItem only swaps Foreground to Black; FontWeight stays Regular. */
    color: #000 !important;
    font-weight: 400 !important;
}

.k-tabs .rz-tabview-panels {
    background: #FFFFFF !important;
    border: none !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    padding: 0 !important;
    margin-top: 12px !important;
}

.k-tabs .rz-tabview-panel { padding: 22px !important; }

/* Desktop tab change is instant — kill Radzen transitions (preserves existing spec §5). */
.k-tabs .rz-tabview-panel,
.k-tabs .rz-tabview-panels { transition: none !important; animation: none !important; }

/* ---------- Card section (CornerRadius=14, BoxShadow 0 0 10 1 #D2D2D2 per App.axaml) ---------- */
.k-card {
    background: #ffffff;
    border-radius: var(--kiq-card-radius);
    box-shadow: var(--kiq-card-shadow);
    border: none;
    margin: var(--kiq-card-margin);
}
.k-card-inner { padding: var(--kiq-card-padding); }
/* §13.8: mapping-card variant — same chrome with the desktop's asymmetric
   Margin="5 5 22 5" (MappingSourceReadOnlyView.axaml): 5px top/left/bottom and a
   22px right inset so the card clears the Mapping tab's right gutter. */
.k-card.k-card--mapping { margin: 5px 22px 5px 5px; }
/* §9: title row is 16 px Bold #4F5766 with an optional question_tip_x11 help
   icon to the right; flex so the icon sits next to the text rather than under
   it. */
.k-section-title {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: var(--kiq-title-font-size);
    font-weight: var(--kiq-title-font-weight);
    color: var(--kiq-title-color);
    margin: 0 0 var(--kiq-card-title-gap);
}
.k-card .k-row { display: flex; align-items: center; gap: 8px; margin: 4px 0; font-size: 12px; color: #333; }
.k-card .k-row strong { color: var(--kiq-text-primary); min-width: 140px; font-weight: 500; }

/* ---------- Action bar ---------- */
.k-action-bar {
    display: flex;
    gap: var(--kiq-button-gap);
    align-items: center;
    padding: 14px 22px;
    flex-wrap: wrap;
    background: #ffffff;
}

/* ---------- DataGrid: 12px text, kiq_selected hover ---------- */
.rz-data-grid, .rz-data-grid .rz-cell-data { font-size: 12px; }
.rz-data-grid .rz-grid-table thead th {
    background: #F6F6F6;
    border-bottom: 1px solid lightgray;
    font-weight: 500;
    color: #333;
}
/* Spec §7.8: DataGridRow:nth-child(even) #F7F7F7 — global zebra-striping. */
.rz-data-grid .rz-grid-table tbody tr:nth-child(even) td { background: #F7F7F7; }
.rz-data-grid .rz-grid-table tbody tr:hover td { background: var(--kiq-selected) !important; }
.rz-data-grid .rz-grid-table tbody tr.rz-state-highlight td { background: var(--kiq-selected) !important; }

/* Row-level desktop-style blue outline on hover (mirrors DataGridRow:pointerover BorderBrush kiq_basic_light). */
.rz-data-grid tr:hover {
    background: var(--kiq-selected);
    box-shadow: inset 0 0 0 1px var(--kiq-basic-light);
}

/* Mirror of DeviceOverrideStatusColorConverter (#FFEDB5) — scanned override rows. */
.rz-data-grid .rz-grid-table tbody tr.k-override-row-scanned td { background: #FFEDB5 !important; }

/* ---------- Card: lose Material shadow ---------- */
.rz-card { box-shadow: none !important; border: 1px solid #e3e3e3; border-radius: 4px; }

/* ---------- Text input: kenergy focus blue ---------- */
/* Spec §7.8: plain TextBox 28px height, border #969696 / focus #006FE6. */
.rz-textbox, .rz-numeric, .rz-form-field input {
    border-color: #969696 !important;
    border-radius: 3px !important;
    font-size: 12px;
    height: 28px;
}
.rz-textbox:focus, .rz-numeric:focus, .rz-form-field input:focus { border-color: var(--kiq-focus) !important; outline: none; }
/* App.axaml TextBox:pointerover border is also #006FE6 — match focus on hover. */
.rz-textbox:hover, .rz-numeric:hover, .rz-form-field input:hover { border-color: var(--kiq-focus) !important; }

/* ---------- Text helpers ---------- */
h1, h2, h3, h4, h5, h6, .rz-text-h1, .rz-text-h2, .rz-text-h3, .rz-text-h4 { color: #111; }
.rz-text-h4 { font-size: 16px; font-weight: 600; margin: 0 0 2px; }
.rz-text-body2 { color: #595959; font-size: 12px; }

/* ---------- Page content padding ---------- */
.rz-body > .rz-p-4 { padding: 12px 16px !important; }

/* ---------- K-shell (KEN-364: MainView.axaml structure — 3 rows × 3 cols) ----------
   Row 0: 1 px LightGray divider header band (no logo bar, menu, or toolbar).
   Row 1: horizontal splitter (0 height — structural mirror of the desktop
          GridSplitter; the header band is fixed, so it has nothing to resize).
   Row 2: 3-column body (sidebar / vertical splitter / content) — see .k-body. */
.k-shell { display: flex; flex-direction: column; min-height: 100vh; background: var(--kiq-app-bg); }

.k-header-band { height: 1px; background: lightgray; width: 100%; flex-shrink: 0; }
.k-row-splitter { height: 0; width: 100%; flex-shrink: 0; }

/* Main body: sidebar + 1px splitter + 1fr content.
   Mirrors the desktop MainView.axaml 1px LightGray GridSplitter.
   Sidebar width is stored per-user in UiSettings and applied as the
   --kiq-sidebar-width CSS variable on .k-shell. Defaults to 360px. */
.k-body { display: grid; grid-template-columns: var(--kiq-sidebar-width, var(--kiq-tree-pane-width)) var(--kiq-splitter-width) 1fr; grid-template-rows: 1fr; flex: 1; min-height: 0; overflow: hidden; }
.k-body .k-sidebar.collapsed { display: none; }
.k-body .k-sidebar.collapsed + .k-splitter + .k-content { grid-column: 1 / span 3; }
.k-sidebar { background: var(--kiq-app-bg); overflow-y: auto; padding: 0; }
.k-splitter {
    width: var(--kiq-splitter-width);
    background: lightgray;
    cursor: col-resize;
    position: relative;
    touch-action: none;
    user-select: none;
}
.k-splitter::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: -3px;
    right: -3px;
}
.k-splitter.dragging { background: var(--kiq-basic, #016FE6); }
/* Desktop convention: the content host (MainView.axaml ContentControl / the
   TabControl ContentPresenter, Margin={TemplateBinding Padding} where Padding=0)
   applies NO container gutter — each view owns its own 22px margin. Tabbed pages
   get their 22px from .rz-tabview-panel below; only the NO-TAB pages (which root
   directly on a .k-card) take the 22px gutter here. */
.k-content { background: var(--kiq-app-bg); overflow-y: auto; }
.k-content:not(:has(> .k-tabs)) { padding: 22px; }
/* When a k-tabs tabview is the direct child, the tab panel scrolls internally —
   mirrors ContentPresenter filling the DockPanel in the desktop App.axaml
   TabControl template. */
.k-content:has(> .k-tabs) { padding: 0; display: flex; flex-direction: column; overflow: hidden; }
.k-content:has(> .k-tabs) .k-tabs.rz-tabview { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.k-content:has(> .k-tabs) .k-tabs .rz-tabview-panels { flex: 1; overflow-y: auto; }

/* ---------- Radio + checkbox sizing ---------- */
.rz-radio-button-box, .rz-checkbox-box { border-color: #969696; }

/* ---------- Parameter list (replica of desktop ListBox Classes="parameters") ----------
   Label stacked above value, arranged as a horizontal flex-wrap that flows across
   the card and wraps to a new row on overflow. Matches the desktop's Status grid
   where State / Operation Status / Occupancy State / Occupancy Probability / Space
   Temperature / Discharge Temperature sit on one row and Heat/Cool Setpoint wrap below. */
.k-param-list {
    display: flex;
    flex-wrap: wrap;
    row-gap: 0;
    column-gap: 0;
    background: transparent;
    margin: 14px 0 0;
    padding: 0;
    list-style: none;
}
.k-param-row {
    display: flex;
    flex-direction: column;
    min-width: 160px;
    gap: 2px;
    line-height: 18px;
}
.k-param-label {
    color: var(--kiq-section-label-color);
    font-weight: var(--kiq-section-label-weight);
    font-size: var(--kiq-section-label-font-size);
    white-space: nowrap;
}
.k-param-value { color: var(--kiq-param-text-default); font-weight: 500; font-size: 13px; }
.k-param-value.k-green  { color: var(--kiq-param-text-green); }
.k-param-value.k-orange { color: var(--kiq-param-text-orange); }
.k-param-value.k-red    { color: var(--kiq-param-text-red); }
.k-param-value.k-blue   { color: var(--kiq-param-text-blue); }
.k-param-value.k-gray   { color: var(--kiq-param-text-gray); }
a.k-param-value.k-param-link { color: var(--kiq-param-text-blue); text-decoration: none; }
a.k-param-value.k-param-link:hover { text-decoration: underline; }

/* ---------- ParameterLabelView chrome (replica of ParameterLabelView.axaml) ----------
   2-column grid: 2 px notification bar on the left + body on the right. The bar normally
   sits at #E9E9E9 and flashes #009EFF for ~300 ms on each value change (driven by the
   .triggered class in ParameterLabel.razor), with a 0.1 s fade matching the desktop's
   BrushTransition. */
.k-parameter-label {
    display: grid;
    grid-template-columns: 2px 1fr;
    column-gap: 16px;
    width: 240px;
    padding-right: 28px;
    box-sizing: border-box;
}
/* spec §8.1: Avalonia BrushTransition uses linear interpolation by default; the
   editable .k-parameter-bar already pins this explicitly, mirror it on the
   read-only label bar so the 0.1 s fade between #E9E9E9 / #009EFF / clean
   matches the desktop's brush ease everywhere a parameter row is rendered. */
.k-parameter-label-bar {
    display: block;
    width: 2px;
    align-self: stretch;
    background: var(--kiq-param-stripe-clean);
    transition: background-color var(--kiq-brush-transition) linear;
}
.k-parameter-label-bar.triggered { background: var(--kiq-param-stripe-flash); }
.k-parameter-label-body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.k-parameter-label-header {
    display: flex;
    align-items: center;
    height: 22px;
    gap: 4px;
}
.k-parameter-label-value {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    margin: 2px 0 12px;
}
/* spec §8.1: ParameterLabelView go-to affordance uses Button.link / Button.external_link
   with bullet_go_gray_x16 (default) and bullet_go_x16 (hover) — desktop palette swap on
   pointer-over, not an opacity fade. */
.k-external-link {
    width: 16px;
    height: 16px;
    padding: 0;
    border: none;
    background: url('icons/bullet_go_gray_x16.png') center center no-repeat;
    cursor: pointer;
}
.k-external-link:hover { background-image: url('icons/bullet_go_x16.png'); }

/* ---------- AI Reasoning timeline (replica of ThermostatView.axaml lines 333-440) ---------- */
.k-timeline {
    display: flex;
    flex-direction: column;
    margin: 14px 0 0;
    max-height: 220px;
    min-height: 120px;
    overflow-y: auto;
    padding-right: 4px;
}
.k-timeline-row {
    display: grid;
    grid-template-columns: auto 24px 1fr;
    align-items: stretch;
    min-height: 40px;
    font-size: 12px;
}
.k-timeline-time {
    color: gray;
    align-self: center;
    text-align: right;
    white-space: nowrap;
    padding-right: 4px;
}
.k-timeline-track {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
}
.k-timeline-track::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 2px;
    margin-left: -1px;
    background: #E0E0E0;
}
.k-timeline-row:first-child .k-timeline-track::before { top: 50%; }
.k-timeline-row:last-child  .k-timeline-track::before { bottom: 50%; }
.k-timeline-dot {
    position: relative;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #4CAF50;
    z-index: 1;
}
.k-timeline-dot.release { background: #9E9E9E; }
.k-timeline-message {
    position: relative;
    padding: 8px 0 8px 8px;
    align-self: center;
    color: #1c1c1c;
    white-space: pre-wrap;
    word-break: break-word;
}
/* ReasoningJson tooltip: hover-revealed, Consolas/monospace, capped at 600px (mirrors
   ThermostatView.axaml ToolTip.FontFamily=Consolas + MaxWidth=600). */
.k-timeline-tooltip {
    display: none;
    position: absolute;
    left: 8px;
    top: 100%;
    z-index: 20;
    margin: 4px 0 0;
    padding: 5px;
    max-width: 600px;
    max-height: 360px;
    overflow: auto;
    font-family: Consolas, "Courier New", monospace;
    font-size: 12px;
    color: #1c1c1c;
    background: #FFFFEC;
    border: 1px solid #C8C8B0;
    border-radius: 3px;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
    white-space: pre-wrap;
    word-break: break-word;
    pointer-events: none;
}
.k-timeline-message:hover .k-timeline-tooltip { display: block; }
.k-timeline-empty {
    text-align: center;
    color: #BEBEBE;
    font-size: 12px;
    padding: 24px 0;
}

/* ---------- Card buttons row ----------
   Desktop Avalonia uses Margin="0 28 0 0" by default, with Margin="0 18 0 0"
   as the tighter alternative (KEN-394 §9). The default rule emits the 28 px
   gap; add the --tight modifier where a card uses the 18 px variant. */
.k-card-buttons {
    display: flex;
    gap: var(--kiq-button-gap);
    align-items: center;
    flex-wrap: wrap;
    margin-top: var(--kiq-card-button-gap);
}
.k-card-buttons--tight { margin-top: var(--kiq-card-button-gap-tight); }

/* ---------- Spaces tab occupancy text colors ---------- */
.k-occ-occupied { color: var(--kiq-basic); }
.k-occ-standby  { color: #E08600; }
.k-occ-unknown  { color: #969696; }

/* ---------- Root About tab: expired license banner (mirrors RootView.axaml) ---------- */
.k-license-expired-banner {
    color: var(--kiq-danger);
    font-weight: 700;
    font-size: 12px;
    margin: 0 0 14px;
}

/* ---------- Tab toolbar row: Refresh + inline search (mirrors DataGrid DockPanel header) ---------- */
.k-toolbar-row {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 0;
    margin-bottom: 12px;
}
.k-search-box {
    flex: 1 1 auto;
    min-height: 29px;
    font-size: 12px;
    padding: 5px 8px;
}

/* ---------- Device Objects row actions: Refresh / Override / Properties links ---------- */
.k-object-row-actions {
    display: flex;
    gap: 10px;
    align-items: center;
}
.k-link-button {
    appearance: none;
    background: transparent;
    border: none;
    padding: 0;
    color: var(--kiq-basic);
    font-size: 12px;
    cursor: pointer;
}
.k-link-button:hover { text-decoration: underline; }
.k-link-button:disabled { color: #B0B0B0; cursor: not-allowed; text-decoration: none; }

/* ---------- ThermostatRepository Overview aggregates (mirrors axaml TextBlock row lines 54-128) ---------- */
.k-repo-aggregates {
    display: flex;
    align-items: flex-end;
    flex-wrap: wrap;
    gap: 0 12px;
    font-size: 12px;
    padding: 12px 0 4px;
}
.k-repo-aggregates .k-agg-label {
    color: #969696;
    margin-right: 2px;
}
.k-repo-aggregates .k-agg-value {
    color: #1c1c1c;
    margin-right: 12px;
}

/* DataGrid cell tint classes — matches ParameterTextColor palette in ParameterList. */
.k-text-red    { color: var(--kiq-param-text-red); }
.k-text-orange { color: var(--kiq-param-text-orange); }
.k-text-green  { color: var(--kiq-param-text-green); }
.k-text-blue   { color: var(--kiq-param-text-blue); }
.k-text-gray   { color: var(--kiq-param-text-gray); }

/* Cloud thermostat device state cell tint — mirrors CloudThermostatDeviceStateColorConverter. */
.k-device-state-online  { color: #65C128; }
.k-device-state-offline { color: var(--kiq-danger); }
.k-device-state-error   { color: #E08600; }
.k-device-state-unknown { color: #000; }

/* ---------- Style-class parity: mirrors App.axaml selectors ---------- */

/* mirrors App.axaml: <Button Classes="kiq_primary" /> */
.kiq-primary-button {
    min-width: 96px;
    height: 28px;
    border-radius: 4px;
    background: var(--kiq-basic);
    border: 1px solid var(--kiq-basic);
    color: #fff;
    font-weight: 400;
    font-size: 12px;
    cursor: pointer;
    padding: 4px 16px;
}
.kiq-primary-button:hover { background: var(--kiq-basic-light); border-color: var(--kiq-basic-light); }
.kiq-primary-button:active { background: var(--kiq-basic-dark); border-color: var(--kiq-basic-dark); }
.kiq-primary-button:disabled { background: var(--kiq-panel-bg); border-color: var(--kiq-panel-bg); color: #D2D2D2; cursor: not-allowed; }

/* mirrors App.axaml: <Button Classes="kiq_secondary" /> */
.kiq-secondary-button {
    min-width: 96px;
    height: 28px;
    border-radius: 4px;
    /* App.axaml Button.kiq_secondary rests gray-filled (#F2F6F9 bg / #CDD3D9
       border / #4F5766 text); the brand-blue outline only appears on hover. */
    background: #F2F6F9;
    border: 1px solid #CDD3D9;
    color: #4F5766;
    font-weight: 400;
    font-size: 12px;
    cursor: pointer;
    padding: 4px 16px;
}
.kiq-secondary-button:hover { border-color: var(--kiq-basic-light); color: var(--kiq-basic-light); }
.kiq-secondary-button:active { background: var(--kiq-basic-background-light); border-color: var(--kiq-basic); color: var(--kiq-basic); }
.kiq-secondary-button:disabled { background: #fff; border-color: #D2D2D2; color: #D2D2D2; cursor: not-allowed; }

/* mirrors App.axaml: <Button Classes="kiq_secondary_danger" /> */
.kiq-secondary-danger-button {
    min-width: 96px;
    height: 28px;
    border-radius: 4px;
    /* App.axaml Button.kiq_secondary_danger rests gray-filled like kiq_secondary
       (#F2F6F9 / #CDD3D9 / #4F5766); the danger-red outline+text only appear on
       hover/press (handled below). */
    background: #F2F6F9;
    border: 1px solid #CDD3D9;
    color: #4F5766;
    font-weight: 400;
    font-size: 12px;
    cursor: pointer;
    padding: 4px 16px;
}
.kiq-secondary-danger-button:hover { border-color: var(--kiq-danger-light); color: var(--kiq-danger-light); }
.kiq-secondary-danger-button:active { background: var(--kiq-danger-background-light); border-color: var(--kiq-danger); color: var(--kiq-danger); }
.kiq-secondary-danger-button:disabled { background: #fff; border-color: #D2D2D2; color: #D2D2D2; cursor: not-allowed; }

/* mirrors App.axaml: <Button Classes="link" /> */
.kiq-link {
    background: transparent;
    border: none;
    /* App.axaml Button.link is a bare ContentPresenter: blue text, hand cursor,
       NO underline. Color shifts on hover/press; disabled hides the link. */
    color: var(--kiq-focus);
    cursor: pointer;
    padding: 0;
    font-size: 12px;
}
.kiq-link:hover { color: #005CBF; }
.kiq-link:active { color: #354C80; }
.kiq-link:disabled { visibility: hidden; }

/* mirrors App.axaml: <Button Classes="external_link" /> — spec §7.8 swaps
   bullet_go_gray_x16 to bullet_go_x16 on hover (palette swap, not a fade). */
.kiq-external-link {
    background: transparent;
    border: none;
    color: var(--kiq-focus);
    text-decoration: underline;
    cursor: pointer;
    padding: 0;
    font-size: 12px;
}
.kiq-external-link::after {
    content: '';
    display: inline-block;
    width: 16px;
    height: 16px;
    margin-left: 4px;
    background-image: url('icons/bullet_go_gray_x16.png');
    background-size: 16px 16px;
    background-repeat: no-repeat;
    vertical-align: middle;
}
.kiq-external-link:hover::after { background-image: url('icons/bullet_go_x16.png'); }

/* mirrors App.axaml line 862 c:ImageToggleButton: MinWidth=96, Margin="0 0 11 0". */
.kiq-image-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    background: #F2F6F9;
    border: 1px solid #CDD3D9;
    color: #4F5766;
    border-radius: 4px;
    min-width: 96px;
    height: 30px;
    margin-right: 11px;
    padding: 0 14px;
    font-size: 12px;
    font-weight: 400;
    cursor: pointer;
    user-select: none;
}
.kiq-image-toggle .kiq-icon { width: 16px; height: 16px; }
.kiq-image-toggle:hover { border-color: var(--kiq-basic-light); color: var(--kiq-basic-light); }
.kiq-image-toggle.active,
.kiq-image-toggle[aria-pressed="true"] {
    background: var(--kiq-basic-background-light);
    border-color: var(--kiq-basic);
    color: var(--kiq-basic);
}
.kiq-image-toggle:disabled,
.kiq-image-toggle[aria-pressed="true"]:disabled {
    background: #fff;
    border-color: var(--kiq-disabled-fg);
    color: var(--kiq-disabled-fg);
    cursor: not-allowed;
}
.kiq-image-toggle:disabled .kiq-icon { filter: var(--kiq-disabled-icon-filter); }

/* mirrors App.axaml lines 262–293: <c:ImageButton Classes="kiq_secondary_danger" />.
   Razor normalizes the underscore form to the hyphen form on the class attribute. */
.kiq-image-button.kiq-secondary-danger,
.kiq-image-button.kiq-secondary {
    background: #F2F6F9;
    border-color: #CDD3D9;
    color: #4F5766;
}
.kiq-image-button.kiq-secondary:hover { border-color: var(--kiq-basic-light); color: var(--kiq-basic-light); }
.kiq-image-button.kiq-secondary:active { background: var(--kiq-basic-background-light); border-color: var(--kiq-basic); color: var(--kiq-basic); }
.kiq-image-button.kiq-secondary-danger:hover { border-color: var(--kiq-danger-light); color: var(--kiq-danger-light); }
.kiq-image-button.kiq-secondary-danger:active { background: var(--kiq-basic-background-light); border-color: var(--kiq-danger); color: var(--kiq-danger); }

/* mirrors App.axaml: DataGrid */
.kiq-grid { font-size: 12px; }
.kiq-grid .rz-grid-table thead th { background: var(--kiq-tab-header-bg); }
.kiq-grid .rz-grid-table tbody tr:hover td { background: var(--kiq-selected) !important; }

/* mirrors App.axaml: TabControl / TabItem */
.kiq-tabs .rz-tabview-nav { background: var(--kiq-tab-header-bg) !important; }
.kiq-tabs .rz-tabview-nav li.rz-state-active { border-top: 3px solid var(--kiq-tab-indicator-color) !important; }
.kiq-tabs .rz-tabview-nav li a { font-size: 12px !important; min-height: 36px; }

/* mirrors App.axaml: TextBox — spec §7.8 height 28px. */
.kiq-textbox {
    /* App.axaml TextBox: BorderBrush #969696, 3px corners; pointerover AND focus
       both go to #006FE6 (no intermediate gray hover). */
    border: 1px solid #969696;
    border-radius: 3px;
    font-size: 12px;
    padding: 0 10px;
    height: 28px;
    background: #fff;
    color: #1c1c1c;
}
.kiq-textbox:hover { border-color: var(--kiq-focus); }
.kiq-textbox:focus { border-color: var(--kiq-focus); outline: none; }
.kiq-textbox:read-only { background: var(--kiq-panel-bg); }

/* mirrors App.axaml: CheckBox (PNG swap) */
.kiq-checkbox { appearance: none; width: 16px; height: 16px; background: url('icons/check_box_uncheck_x16.png') no-repeat center; background-size: 16px 16px; cursor: pointer; }
.kiq-checkbox:checked { background-image: url('icons/check_box_x16.png'); }
/* App.axaml CheckBox:pointerover dims to Opacity 0.8. */
.kiq-checkbox:hover { opacity: 0.8; }

/* Spec §7.8: plain ComboBox — App.axaml lines 1387-1416 set Background=White,
   BorderBrush=#969696 (focus #006FE6), Height=28, FontSize=12. The Radzen
   icon-font chevron is masked into the desktop's downward-triangle path —
   colour the mask #595959 normally, #fff once the popup opens (mirrors
   ComboBox[IsDropDownOpen=True] /template/ Path#PART_Path Fill=White).
   .kiq-search-combo-wrap and .kiq-combo-period carry higher-specificity
   restyles that override this base where the spec calls for a distinct chrome. */
.rz-dropdown {
    height: 28px;
    min-height: 28px;
    background: #fff;
    border: 1px solid #969696;
    border-radius: 3px;
    font-size: 12px;
    color: #1c1c1c;
}
.rz-dropdown.rz-state-focused,
.rz-dropdown:focus-within { border-color: var(--kiq-focus); }
.rz-dropdown.rz-state-disabled,
.rz-dropdown[aria-disabled="true"] {
    background: #fff;
    border-color: #D2D2D2;
    color: #D2D2D2;
}
.rz-dropdown .rz-dropdown-trigger-icon {
    width: 8px;
    height: 4px;
    background-color: #595959;
    -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 4'><path d='M0 0 L8 0 L4 4 Z' fill='black'/></svg>");
            mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 4'><path d='M0 0 L8 0 L4 4 Z' fill='black'/></svg>");
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    -webkit-mask-position: center;
            mask-position: center;
    -webkit-mask-size: 8px 4px;
            mask-size: 8px 4px;
    color: transparent;
    font-size: 0;
}
.rz-dropdown.rz-dropdown-open .rz-dropdown-trigger-icon,
.rz-dropdown[aria-expanded="true"] .rz-dropdown-trigger-icon {
    background-color: #fff;
}
.rz-dropdown .rz-dropdown-label {
    padding-inline-start: 10px;
    padding-inline-end: 28px;
    display: flex;
    align-items: center;
    gap: 6px;
}
.rz-dropdown .rz-dropdown-label .kiq-icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

/* mirrors App.axaml: ComboBox.period — spec §7.8 height 29px, F2F6F9 / #CDD3D9. */
.kiq-combo-period {
    height: 29px;
    min-width: 145px;
    margin: 0 11px 0 0;
    border: 1px solid #CDD3D9;
    border-radius: 4px;
    background: #F2F6F9;
    font-size: 12px;
    color: #4F5766;
    /* App.axaml Padding="10 0 0 0" (left 10); the 28px right keeps room for the
       web chevron/calendar glyph the desktop draws in its template. */
    padding: 0 28px 0 10px;
    background-image: url('icons/calendar_view_month_x16.png');
    background-repeat: no-repeat;
    background-position: right 6px center;
    background-size: 16px 16px;
}
/* App.axaml ComboBox.period:pointerover — opacity 0.8, border+text kiq_basic_light. */
.kiq-combo-period:hover { opacity: 0.8; border-color: var(--kiq-basic-light); color: var(--kiq-basic-light); }
/* :disabled — White bg, #D2D2D2 border + text. */
.kiq-combo-period:disabled { background: #fff; border-color: var(--kiq-disabled-fg); color: var(--kiq-disabled-fg); }

/* mirrors App.axaml: c:NumericUpDownUnits (lines 448–688) */
.kiq-numeric-units {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--kiq-basic-gray);
    border-radius: 3px;
    height: 28px;
    overflow: hidden;
    font-size: 12px;
    background: transparent;
    color: #1c1c1c;
}
.kiq-numeric-units:hover,
.kiq-numeric-units:focus-within { border-color: var(--kiq-focus); }
/* Spec §7.3: value is left-aligned; units suffix sits to the right in #969696. */
.kiq-numeric-units input { border: none; outline: none; flex: 1 1 auto; min-width: 0; width: auto; text-align: left; padding: 0 4px; font-size: 12px; background: transparent; color: inherit; }
.kiq-numeric-units input[readonly] { cursor: default; }
/* App.axaml 525-533: TextBlock#PART_Units uses Margin="0 1 0 0" — top-1px nudge
   aligns the units baseline with the numeric input. */
.kiq-numeric-units .kiq-unit-label { color: var(--kiq-basic-gray); padding: 0 6px; margin-top: 1px; font-size: 12px; flex-shrink: 0; white-space: nowrap; }
.kiq-numeric-units .kiq-spinner-up,
.kiq-numeric-units .kiq-spinner-down {
    width: 20px;
    height: 14px;
    background: var(--kiq-stepper-bg);
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #595959;
}
/* App.axaml: top button CornerRadius="0 3 0 0", bottom "0 0 3 0" */
.kiq-numeric-units .kiq-spinner-up { border-radius: 0 3px 0 0; }
.kiq-numeric-units .kiq-spinner-down { border-radius: 0 0 3px 0; }
.kiq-numeric-units .kiq-spinner-up:hover,
.kiq-numeric-units .kiq-spinner-down:hover { background: #CCE2FA; }
.kiq-numeric-units .kiq-spinner-up:active,
.kiq-numeric-units .kiq-spinner-down:active { background: #7FB7F2; }
.kiq-numeric-units .kiq-spinner-up:disabled,
.kiq-numeric-units .kiq-spinner-down:disabled { background: var(--kiq-stepper-bg); opacity: 0.3; cursor: not-allowed; }

/* mirrors App.axaml: ButtonSpinner (up/down arrows) — standalone usage */
.kiq-spinner-up,
.kiq-spinner-down {
    background: var(--kiq-stepper-bg);
    border: none;
    cursor: pointer;
    width: 20px;
    height: 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #595959;
}
.kiq-spinner-up:hover,
.kiq-spinner-down:hover { background: #CCE2FA; }
.kiq-spinner-up:active,
.kiq-spinner-down:active { background: #7FB7F2; }
.kiq-spinner-up:disabled,
.kiq-spinner-down:disabled { opacity: 0.3; cursor: not-allowed; }

/* mirrors App.axaml: SearchComboBox */
.kiq-search-combo {
    height: 28px;
    border: 1px solid var(--kiq-basic-gray);
    border-radius: 3px;
    font-size: 12px;
    padding: 0 8px;
}
.kiq-search-combo:focus { border-color: var(--kiq-focus); outline: none; }

/* mirrors App.axaml: ListBox Classes="autocomplete" — Material 3 level 2 menu surface */
.kiq-autocomplete-list {
    box-shadow: var(--m3-elevation-2);
    border-radius: 4px;
    /* App.axaml ListBox.autocomplete: item surface #F2F6F9 (border #CDD3D9),
       hover/selected #E6E6E6, pressed #DBDBDB. */
    background: #F2F6F9;
    border: 1px solid #CDD3D9;
    padding: 4px 0;
}
.kiq-autocomplete-list li { padding: 4px 10px; font-size: 12px; cursor: pointer; background: #F2F6F9; }
.kiq-autocomplete-list li:hover { background: #E6E6E6; }
.kiq-autocomplete-list li:active { background: #DBDBDB; }

/* mirrors App.axaml: ListBox Classes="parameters" */
.kiq-parameter-wrap {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    list-style: none;
    padding: 0;
    margin: 0;
}

/* ---------- TimeSeriesChart legend (matches assets/screenshots/HP-3-15.png) ----------
   Single horizontal row above the SVG: small colored swatch + name, ~16 px between
   items, dark text. Hidden series fade out to ~40 % opacity. */
.kiq-legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 18px;
    padding: 6px 8px 10px;
    min-height: 28px;
}
.kiq-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 0;
    margin: 0;
    border: none;
    background: transparent;
    font-family: inherit;
    font-size: 11px;
    font-weight: 500;
    color: #1c1c1c;
    line-height: 1;
    cursor: pointer;
    white-space: nowrap;
}
.kiq-legend-item:hover { color: #000; }
.kiq-legend-item .kiq-legend-swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 1px;
    flex-shrink: 0;
}
.kiq-legend-item.kiq-legend-hidden { opacity: 0.4; }
.kiq-legend-item.kiq-legend-hidden .kiq-legend-swatch {
    background: #C8CCD0 !important;
}

/* mirrors App.axaml: MultipleTrackerPlot overlay (popup Border Background
   #99000000 = rgba(0,0,0,0.6), CornerRadius 2, MinWidth 180, MaxHeight 500,
   10 px white text; swatch is a 14×5 rectangle, not a dot). The live tracker is
   styled by the higher-specificity scoped copy in TimeSeriesChart.razor.css —
   this global copy is kept in sync as the documented baseline. */
.kiq-plot-tracker {
    min-width: 180px;
    max-height: 500px;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    font-size: 10px;
    padding: 6px 10px;
    border-radius: 2px;
    pointer-events: none;
    white-space: nowrap;
}
.kiq-plot-tracker .kiq-tracker-dot {
    display: inline-block;
    width: 14px;
    height: 5px;
    border-radius: 0;
    margin-right: 4px;
    vertical-align: middle;
}

/* ---------- ImageExtended (c:ImageExtended replica) ---------- */
/* §6 disabled-state grayscale pass. The Avalonia Skia pixel pass is
   0.299R + 0.587G + 0.114B with preserved alpha; the spec's close CSS
   approximation is grayscale(1) brightness(0.85). Exposed as a variable
   so any disabled icon site can opt in. */
:root { --kiq-disabled-icon-filter: grayscale(1) brightness(0.85); }
.kiq-image-extended {
    display: block;
    image-rendering: -webkit-optimize-contrast;
    image-rendering: pixelated;
    transition: filter .15s ease, opacity .15s ease;
}
.kiq-image-extended.kiq-disabled,
.kiq-image-extended[aria-disabled="true"] { filter: var(--kiq-disabled-icon-filter); }

/* §6 catalog: every kiq-icon img is a PNG sprite from wwwroot/icons.
   Pixel pass keeps 16×16 / 32×32 sprites crisp; disabled state uses the
   brief's grayscale approximation rather than opacity. Brief allows
   either -webkit-optimize-contrast or pixelated — layer both so modern
   browsers pick the standard value and older WebKit retains the
   vendor-prefixed fallback. */
.kiq-icon {
    image-rendering: -webkit-optimize-contrast;
    image-rendering: pixelated;
}
.kiq-icon.kiq-disabled,
.kiq-icon[aria-disabled="true"],
:disabled > .kiq-icon,
[aria-disabled="true"] > .kiq-icon { filter: var(--kiq-disabled-icon-filter); }

/* §6 brief: image-rendering hint applies globally to any <img> served from
   /icons/, not just class-tagged sprites. Layer both values so modern
   browsers pick the standard `pixelated` and older WebKit keeps the
   vendor-prefixed fallback. Disabled siblings get the same grayscale
   approximation of the desktop ImageExtended Skia pass. */
img[src*="/icons/"],
img[src^="icons/"] {
    image-rendering: -webkit-optimize-contrast;
    image-rendering: pixelated;
}
:disabled img[src*="/icons/"],
[aria-disabled="true"] img[src*="/icons/"],
img[src*="/icons/"].kiq-disabled,
img[src*="/icons/"][aria-disabled="true"] { filter: var(--kiq-disabled-icon-filter); }

/* ---------- ImageFlicker (c:ImageFlicker replica) ---------- */
@keyframes kiq-flicker {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.3; }
}
.kiq-image-flicker {
    animation: kiq-flicker 500ms linear infinite;
    animation-play-state: paused;
}

/* ---------- ImageFrame (c:ImageFrame replica) ---------- */
.kiq-image-frame {
    display: inline-block;
    background: #fff;
}

/* ---------- NumericUpDownUnits spinner column (App.axaml lines 592–640: DockPanel.Dock="Right") ---------- */
.kiq-numeric-units .kiq-spinner-col {
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    border-left: 1px solid #E0E0E0;
    height: 100%;
}
.kiq-numeric-units .kiq-spinner-chevron {
    display: block;
    pointer-events: none;
}
.kiq-numeric-units.disabled {
    opacity: 0.5;
    pointer-events: none;
}
.kiq-numeric-units input::placeholder { color: #969696; }

/* ---------- SearchCombobox wrapper (App.axaml c:SearchComboBox, lines 1167–1310) ---------- */
.kiq-search-combo-wrap { display: inline-block; }
.kiq-search-combo-wrap .rz-dropdown {
    height: 28px;
    min-height: 28px;
    border: 1px solid #969696;
    border-radius: 4px;
    font-size: 12px;
    background: transparent;
}
/* App.axaml 1293-1295: pointerover border #006FE6 */
.kiq-search-combo-wrap .rz-dropdown:hover { border-color: var(--kiq-focus); }
/* App.axaml 1274-1277: focus-within border #006FE6 */
.kiq-search-combo-wrap .rz-dropdown:focus-within { border-color: var(--kiq-focus); }
/* App.axaml 1297-1301: disabled background white, border + text #D2D2D2 */
.kiq-search-combo-wrap .rz-dropdown.rz-state-disabled,
.kiq-search-combo-wrap .rz-dropdown[aria-disabled="true"] {
    background: #fff;
    border-color: #D2D2D2;
    color: #D2D2D2;
}
/* App.axaml 1303-1305: open popup clips the bottom corners of the border */
.kiq-search-combo-wrap .rz-dropdown.rz-state-focused.rz-dropdown-open,
.kiq-search-combo-wrap .rz-dropdown[aria-expanded="true"] {
    border-radius: 4px 4px 0 0;
}
/* App.axaml 1289-1291: ^[IsPopupOpen=True] /template/ Path#PART_Path Fill=White —
   chevron flips to white once the popup opens to read against the focused border. */
.kiq-search-combo-wrap .rz-dropdown.rz-dropdown-open .rz-dropdown-trigger-icon,
.kiq-search-combo-wrap .rz-dropdown[aria-expanded="true"] .rz-dropdown-trigger-icon {
    color: #fff;
}
/* Spec §7.4: toggle (trigger) button suppressed when filtering yields no
   match, or a single match equal to the current text — opening the popup
   would be redundant. The wrapper sets .kiq-toggle-disabled when either
   condition holds; the rule pointer-blocks the trigger and dims its glyph. */
.kiq-search-combo-wrap.kiq-toggle-disabled .rz-dropdown-trigger {
    pointer-events: none;
    opacity: 0.35;
}

/* Spec §7.4: dropdown popup items follow ListBox.autocomplete — bg F2F6F9,
   hover E6E6E6, pressed DBDBDB. The Radzen popup is rendered outside the
   wrapper at document root, so the rules must target .rz-dropdown-panel
   globally rather than scoping under .kiq-search-combo-wrap. */
.rz-dropdown-panel .rz-dropdown-items,
.rz-dropdown-panel .rz-dropdown-filter-container {
    background: #F2F6F9;
}
.rz-dropdown-panel .rz-dropdown-item { background: #F2F6F9; font-size: 12px; padding: 4px 10px; }
.rz-dropdown-panel .rz-dropdown-item:hover,
.rz-dropdown-panel .rz-dropdown-item.rz-state-highlight:hover { background: #E6E6E6; }
.rz-dropdown-panel .rz-dropdown-item:active,
.rz-dropdown-panel .rz-dropdown-item.rz-state-highlight { background: #DBDBDB; }

/* ---------- AutoScrollListBox (c:AutoScrollListBox replica) ---------- */
.kiq-auto-scroll-list {
    position: relative;
    overflow-y: auto;
    max-height: 300px;
    border: 1px solid #E0E0E0;
    border-radius: 4px;
}
.kiq-auto-scroll-content { padding: 4px 0; }

/* App.axaml lines 423–438: 1 s LightGray→Orange→LightGray pulse on Button.animated border.
   The pulse fires only when new items arrive while the user is scrolled up — Button.scrolling
   stays static gray until that happens. */
@keyframes kiq-pulse {
    0%, 100% { border-color: #D3D3D3; }
    50%      { border-color: #FFA500; }
}
/* App.axaml 347-352: Grid with RowDefinitions="*,20" — the scroll-to-bottom button
   occupies a 20 px row stretched across the bottom of the list, not a corner chip. */
.kiq-scroll-to-bottom {
    position: absolute;
    bottom: 4px;
    left: 8px;
    right: 8px;
    background: transparent;
    color: #595959;
    border: 1px solid #D3D3D3;
    border-radius: 2px;
    height: 20px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    z-index: 1;
}
.kiq-scroll-to-bottom.animated { animation: kiq-pulse 1s linear infinite; }
.kiq-scroll-to-bottom:hover { background: #E3F3FF; }
.kiq-scroll-to-bottom img { display: block; }

/* ---------- Parameter components (replica of KenergyUI Parameters/Views/*.axaml) ----------
   2-column grid mirroring spec 8.1: 2 px left stripe (#E9E9E9 clean, #FFB900 dirty)
   + flex body that stacks header / editor / error-line. The stripes from sibling rows
   align so a section forms the desktop's "joined-by-lines" column. The read-only
   ParameterLabelView keeps its own .k-parameter-label-bar (009EFF notification flash).
   Widths/heights tuned to match assets/screenshots/HP-3-15-general.png. */
/* spec §8.2: section container is a WrapPanel-like horizontal wrap. Every row is
   fixed 240 px wide with a 28 px right gutter so adjacent columns stack with the
   spec gap and vertically-stacked rows share the same 2 px left stripe column,
   yielding the desktop "joined-by-lines" effect. Backgrounds are transparent —
   the section itself adds no chrome, and rows are not selectable. Matches the
   desktop ListBox Classes="parameters" container. */
.k-parameter-section {
    display: flex;
    flex-wrap: wrap;
    row-gap: 0;
    column-gap: 0;
    background: transparent;
    margin: 0;
    padding: 0;
    list-style: none;
}
.k-parameter {
    display: grid;
    /* spec §8.1 Avalonia stripe Margin="0 0 16 0" — 16 px gap between the 2 px
       stripe cell and the editor body. The row's outer width is fixed at 240 px
       so a stack of these rows shares the same stripe column. */
    grid-template-columns: 2px 1fr;
    column-gap: 16px;
    width: 240px;
    padding-right: 28px;
    box-sizing: border-box;
    background: transparent;
}
.k-parameter-bar {
    /* spec §8.1: HorizontalAlignment="Left" VerticalAlignment="Stretch" — the
       grid cell stretches by default but force display:block + align-self so
       the stripe paints the full row height even when the body has no editor
       (ParameterBool inplace, ParameterLabel slot variants). */
    display: block;
    width: 2px;
    align-self: stretch;
    background: var(--kiq-param-stripe-clean);
    transition: background-color var(--kiq-brush-transition) linear;
}
.k-parameter-bar.k-dirty { background: var(--kiq-param-stripe-dirty); }
.k-parameter-body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
/* spec §8.1: label SelectableTextBlock Margin="0 4 4 0" (bottom=4) + help-icon
   Image Margin="0 3 4 0" (bottom=4). Express both bottoms on the header itself
   so the editor row starts exactly 4 px below the 22 px header band. */
.k-parameter-header {
    display: flex;
    align-items: center;
    height: 22px;
    gap: 4px;
    margin-bottom: 4px;
}
.k-parameter-name {
    font-size: 12px;
    font-weight: 700;
    color: #000;
    white-space: nowrap;
    /* spec §8.1: desktop SelectableTextBlock has Background="White" so the
       selection chrome reads cleanly over any patterned container. The web
       <span> inherits transparent by default; pin it to white here to match
       the desktop visual contract, particularly inside dialog cards that
       carry their own surface tint. */
    background: #fff;
}
.k-parameter-tip {
    width: 11px;
    height: 11px;
    cursor: help;
}
.k-parameter-editor { display: flex; align-items: center; }
/* spec §8.3: ParameterDecimalView uses KNumericUpDownUnits 128×28. The shared
   .kiq-numeric-units chrome (App.axaml lines 448-688) sizes to content by default;
   lock the editor cell width inside parameter rows so the desktop's 128 px reads. */
.k-parameter-editor > .kiq-numeric-units { width: 128px; }
.k-parameter-editor > .kiq-numeric-units input { flex: 1 1 auto; width: 100%; }
.k-parameter-error {
    font-size: var(--kiq-error-font-size);
    font-weight: var(--kiq-error-font-weight);
    color: var(--kiq-error-text);
    margin-top: 2px;
    min-height: 16px;
    white-space: normal;
}
.k-parameter-error::before { content: '\1F7AB\00a0'; }
/* spec §8.5: error row is always present (16 px reserved), but the glyph + text
   only render when the slot carries a message — mirrors the desktop's
   IsVisible="{Binding ErrorMessage, …IsNotNullOrEmpty}" on the inner TextBlock. */
.k-parameter-error.is-empty::before { content: none; }
.k-parameter-inplace { display: inline-flex; align-items: center; min-width: 0; flex: 1; }
.k-parameter-inplace .kiq-textbox { width: 208px; height: 32px; font-size: 13px; }

/* Combobox skin matching desktop ComboBox in parameter views (spec §8.3:
   ParameterSelectableView ComboBox Width 176 Height 28, font 12). */
.k-parameter-combobox {
    height: 28px;
    width: 176px;
    border: 1px solid #969696;
    border-radius: 4px;
    background: #fff;
    font-size: 12px;
    padding: 0 28px 0 8px;
    color: #1c1c1c;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='%23595959' d='M0 0l5 6 5-6z'/></svg>");
    background-repeat: no-repeat;
    background-position: right 10px center;
    cursor: pointer;
}
.k-parameter-combobox:hover { border-color: #9DA4AB; }
.k-parameter-combobox:focus { border-color: var(--kiq-focus); outline: none; }
.k-parameter-combobox:disabled { background-color: var(--kiq-panel-bg); color: #969696; cursor: default; }
/* spec §8.3: ParameterSelectableView 'N/A' placeholder is rendered in gray
   (Avalonia PlaceholderForeground=Gray). Native <select> shows the disabled
   selected option in default text color, so swap to gray when the placeholder
   is what's currently visible. */
.k-parameter-combobox.is-placeholder { color: #969696; }

/* TimeRange (From / — / To) specific sizing; mirrors 69px ComboBox widths in desktop. */
/* Desktop ParameterTimeRangeView places the enable CheckBox in the header row
   (StackPanel Grid.Row=0) before the Name; mirror that here with a 5 px right
   margin matching the Avalonia CheckBox Margin="0 0 5 0". */
.k-parameter-timerange-enable { margin-right: 5px; }
.k-parameter-timerange {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}
.k-parameter-timerange select {
    width: 69px;
    height: 28px;
    border: 1px solid var(--kiq-basic-gray);
    border-radius: 3px;
    background: #fff;
    font-size: 12px;
    padding: 0 4px;
}
.k-parameter-timerange select:disabled { background: var(--kiq-panel-bg); color: #969696; }
.k-parameter-timerange .k-parameter-timerange-dash {
    font-size: 12px;
    margin: 0 2px;
    color: #1c1c1c;
}

/* Filter input (search + clear glyphs).
   spec §8.3: KSearchComboBox W=176 Padding="18 8 15 5" (Avalonia
   left/top/right/bottom). The 18 px left / 15 px right pads reserve room for
   the search glyph + clear (×) button that the desktop SearchComboBox chrome
   anchors outside the inner TextBox content area. We mirror that on web by
   absolutely positioning .k-parameter-filter-icon at the left chrome edge and
   .k-parameter-filter-clear at the right edge, then padding the inner <input>
   per spec so the typed text and watermark land where the desktop does. */
.k-parameter-filter-wrap {
    position: relative;
    display: inline-flex;
    align-items: stretch;
    width: 176px;
    height: 28px;
    border: 1px solid var(--kiq-basic-gray);
    border-radius: 3px;
    background: #fff;
    box-sizing: border-box;
}
.k-parameter-filter-wrap:focus-within { border-color: var(--kiq-focus); }
.k-parameter-filter-wrap input {
    flex: 1;
    min-width: 0;
    border: none;
    outline: none;
    font-size: 12px;
    background: transparent;
    padding: 8px 15px 5px 18px;
    box-sizing: border-box;
}
.k-parameter-filter-wrap .k-parameter-filter-icon {
    position: absolute;
    left: 4px;
    top: 50%;
    transform: translateY(-50%);
    width: 12px;
    height: 12px;
    opacity: 0.6;
    pointer-events: none;
}
.k-parameter-filter-wrap .k-parameter-filter-clear {
    position: absolute;
    right: 2px;
    top: 50%;
    transform: translateY(-50%);
    border: none;
    background: transparent;
    color: #969696;
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    padding: 0 2px;
}
.k-parameter-filter-wrap .k-parameter-filter-clear:hover { color: var(--kiq-danger); }

/* Inplace toggle uses existing .kiq-image-toggle; nothing extra. */

/* ---------- Dialog shell (mirror of KenergyUI DialogWindow + DialogConfirmView) ---------- */
:root {
    --k-dialog-pad-x: 20px;
    /* Modal dialog drop shadow — colors_and_type.css --shadow-dialog, sourced
       from the Avalonia DialogConfirmView. Replaces the generic M3 elevation
       so the web dialog matches the design handoff exactly. */
    --kiq-shadow-dialog: 0 4px 16px 2px rgba(89, 89, 89, 0.30);
}
.k-dialog {
    background: #ffffff;
    color: #4F5766;
    font-family: inherit;
    font-size: 12px;
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    /* Card chrome lives on .k-dialog now (not on .rz-dialog) so the inner
       card is the visible dialog regardless of Radzen's wrapper styling.
       The 1 px Gray outer border mirrors KenergyUI DialogWindow chrome
       (KEN-361 §12 shared shell). */
    border: 1px solid lightgray;
    border-radius: 14px;
    box-shadow: var(--kiq-shadow-dialog);
    /* Viewport-relative caps so the dialog never exceeds the visible area
       regardless of how the Radzen flex chain resolves height. The 32px
       subtracted matches the .rz-dialog-wrapper safe-area padding. */
    max-width: calc(100vw - 32px);
    max-height: calc(100vh - 32px);
    overflow: hidden;
    /* Belt-and-suspenders centering: if any ancestor in the Radzen chain
       (`.rz-dialog`, `.rz-dialog-content`) ends up larger than the dialog —
       e.g. when Radzen's `width: 100% !important` wins over the `:has()`
       override on a stale browser — `margin: auto` still centers `.k-dialog`
       within it on both axes. */
    margin: auto;
}
/* Semantic size tokens — viewport-driven widths that fill the available
   safe area on narrow screens and cap at a desktop max on wide screens.
   Use Size= on DialogShell (preferred) instead of fixed Width=. The 32px
   subtracted from 100vw matches the `.rz-dialog-wrapper` safe-area padding,
   so the dialog visually fits flush against the wrapper's 16px inset on
   each side when fluid. The cap values mirror the desktop DialogWindow
   widths. Using `min(calc(100vw - X), max)` instead of the old
   `min(100%, clamp(min, vw, max))` removes the parent-relative `100%` term
   that could collapse to 0 when Radzen's `.rz-dialog` is autosized — that
   collapse left the dialog visually anchored to one edge instead of
   centered on certain viewports. */
.k-dialog.k-size-loading { width: min(calc(100vw - 32px), 300px); }
.k-dialog.k-size-compact { width: min(calc(100vw - 32px), 320px); }
.k-dialog.k-size-default { width: min(calc(100vw - 32px), 420px); }
.k-dialog.k-size-wide    { width: min(calc(100vw - 32px), 1060px); }
.k-dialog.k-size-full    { width: min(calc(100vw - 32px), 1240px); }
.k-dialog-content {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    /* KEN-361 §12.1 header band: 32 px vertical margin around the 32x32
       icon + title/message StackPanel. Mirrored here as 32 px top padding
       on the content frame and a matching gap below the header row. */
    padding-top: 32px;
    padding-bottom: 12px;
    overflow: hidden;
}
.k-dialog-header {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 12px;
    padding: 0 var(--k-dialog-pad-x) 32px;
}
.k-dialog-icon {
    width: 32px;
    height: 32px;
    flex: 0 0 32px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.k-dialog-icon img {
    image-rendering: auto;
}
.k-dialog-body {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 0 var(--k-dialog-pad-x);
}
.k-dialog-title {
    font-size: 16px;
    font-weight: 700;
    color: #4F5766;
    line-height: 1.25;
}
.k-dialog-message {
    font-size: 12px;
    color: #4F5766;
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    min-width: 0;
}
.k-dialog-text {
    font-size: 12px;
    color: #4F5766;
}
.k-dialog-progress {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 6px;
    font-size: 12px;
    color: #4F5766;
    line-height: 1.5;
}
.k-dialog-progress-message {
    flex: 1 1 auto;
    min-width: 0;
}
/* KEN-412 §12.3b: tabular-nums keeps the percentage from shifting horizontally
   as digits change during progress (e.g. 9% → 10% → 100%). */
.k-dialog-progress-text {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    color: #969696;
}
.k-dialog-separator {
    height: 1px;
    background: lightgray;
    width: 100%;
}
.k-dialog-footer {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    padding: 12px var(--k-dialog-pad-x);
    gap: 8px;
    flex-wrap: wrap;
    row-gap: 6px;
}
.k-dialog-footer-left,
.k-dialog-footer-right {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}
/* §12.1: 11 px spacing between footer buttons on both sides — the desktop
   DialogConfirmView uses Margin="11 0 0 0" between the left Discard/captcha pair
   and between the right Cancel/Confirm pair. */
.k-dialog-footer-left { gap: 11px; }
.k-dialog-footer-right { margin-left: auto; gap: 11px; }
.k-dialog-footer-right > .kiq-button { flex: 0 0 auto; }

/* Dialog buttons — exact mirror of the kiq button palette from
   KenergyUI/Kenergy/App.axaml (Button.kiq_primary / kiq_secondary /
   kiq_secondary_danger). Each state (idle/hover/pressed/disabled) uses
   discrete colors rather than opacity so the modal footer reads identical
   to the desktop reference (KEN-382). */
.kiq-button {
    min-width: clamp(80px, 22vw, 100px);
    height: 28px;
    padding: 0 10px;
    font-family: inherit;
    font-size: 12px;
    border-radius: 3px;
    cursor: pointer;
    border: 1px solid #CDD3D9;
    background: #F2F6F9;
    color: #4F5766;
}
.kiq-button:disabled { cursor: default; }

.kiq-button.kiq-primary {
    background: var(--kiq-basic);
    border-color: var(--kiq-basic);
    color: #ffffff;
}
.kiq-button.kiq-primary:hover {
    background: var(--kiq-basic-light);
    border-color: var(--kiq-basic-light);
}
.kiq-button.kiq-primary:active {
    background: var(--kiq-basic-dark);
    border-color: var(--kiq-basic-dark);
}
.kiq-button.kiq-primary:disabled {
    background: #F0F0F0;
    border-color: #F0F0F0;
    color: #D2D2D2;
}

.kiq-button.kiq-secondary {
    background: #F2F6F9;
    border-color: #CDD3D9;
    color: #4F5766;
}
.kiq-button.kiq-secondary:hover {
    background: #F2F6F9;
    border-color: var(--kiq-basic-light);
    color: var(--kiq-basic-light);
}
.kiq-button.kiq-secondary:active {
    background: var(--kiq-basic-background-light);
    border-color: var(--kiq-basic);
    color: var(--kiq-basic);
}
.kiq-button.kiq-secondary:disabled {
    background: #ffffff;
    border-color: #D2D2D2;
    color: #D2D2D2;
}

.kiq-button.kiq-secondary-danger {
    background: #F2F6F9;
    border-color: #CDD3D9;
    color: #4F5766;
}
.kiq-button.kiq-secondary-danger:hover {
    background: #F2F6F9;
    border-color: var(--kiq-danger-light);
    color: var(--kiq-danger-light);
}
.kiq-button.kiq-secondary-danger:active {
    background: var(--kiq-danger-background-light);
    border-color: var(--kiq-danger);
    color: var(--kiq-danger);
}
.kiq-button.kiq-secondary-danger:disabled {
    background: #ffffff;
    border-color: #D2D2D2;
    color: #D2D2D2;
}

/* Keep Radzen's dialog chrome invisible so DialogShell owns the look:
   the gear-icon + title sit at the top of the inner card; the outer
   Radzen titlebar (and its X close button) must never render alongside,
   which would duplicate the heading. */
.rz-dialog-wrapper:has(.k-dialog) > .rz-dialog-titlebar,
.rz-dialog:has(.k-dialog) > .rz-dialog-titlebar { display: none; }

/* Radzen's wrapper is a fixed full-viewport flex container. Add a safe-area
   padding so dialogs never sit flush against the viewport edges, and
   re-declare the flex centering explicitly so the dialog stays centered
   dynamically across viewport widths even if Radzen's base rule is ever
   overridden by theme cascade order. The inner .k-dialog inherits its max
   width/height from this padded box. */
.rz-dialog-wrapper:has(.k-dialog) {
    box-sizing: border-box;
    padding: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Radzen sets `.rz-dialog` to `position: absolute` with inline `left`/`top`
   styles computed in JS. That breaks responsive sizing — the dialog can clip
   off-screen on small viewports, and the inner clamp() widths get ignored.
   Switch to flex centering by neutralizing the inline absolute positioning
   so the wrapper's flex centering takes over; strip Radzen's chrome (the
   `.k-dialog` carries its own card chrome) so we don't get a double-card. */
.rz-dialog:has(.k-dialog) {
    position: relative !important;
    left: auto !important;
    top: auto !important;
    inset: auto !important;
    width: auto !important;
    height: auto !important;
    min-width: 0 !important;
    min-height: 0 !important;
    max-width: 100% !important;
    max-height: 100% !important;
    margin: 0 !important;
    background: transparent !important;
    box-shadow: none !important;
    border-radius: 0 !important;
    overflow: visible !important;
    display: flex;
    flex-direction: column;
}

/* Radzen's content slot wraps our `.k-dialog`. Strip its padding/scroll so
   the inner `.k-dialog-body` is the single source of scrolling, and make it
   flex-fill so `.k-dialog`'s `max-height` has a parent to anchor against. */
.rz-dialog-content:has(.k-dialog) {
    padding: 0 !important;
    overflow: visible !important;
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
}

/* Parameter-dialog chrome (mirror of DialogParameter*View.axaml).
   Header / body / footer all share --k-dialog-pad-x now, so .no-icon needs
   no special padding override — only its (absent) icon column to collapse. */
.k-dialog-param-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 2px;
}
/* Right-align the standalone relinquish link sitting above the editor in
   DialogParameterString / Decimal / List (the title comes from DialogShell,
   so the relinquish link gets its own row). Mirrors the desktop axaml,
   which puts the link at the top-right of the editor row. */
.k-dialog-param-relinquish-row {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 2px;
}
.k-dialog-param-name {
    font-size: 12px;
    font-weight: 700;
    color: #4F5766;
}
.k-dialog-param-relinquish {
    background: none;
    border: none;
    padding: 0;
    font-size: 10px;
    color: var(--kiq-basic);
    text-decoration: underline;
    cursor: pointer;
}
.k-dialog-param-relinquish:disabled { color: #969696; cursor: default; text-decoration: none; }
.k-dialog-param-error {
    margin-top: 2px;
    font-size: 10px;
    font-weight: 600;
    color: var(--kiq-danger);
}

/* Override-dialog chrome (mirror of DialogOverride*View.axaml). */
.k-dialog-override-title {
    font-size: 16px;
    font-weight: 700;
    color: #4F5766;
    line-height: 1.25;
    margin-bottom: 4px;
    word-break: break-word;
}
.k-dialog-override-description {
    font-size: 12px;
    color: #4F5766;
    line-height: 1.5;
    white-space: pre-wrap;
    margin-bottom: 4px;
}
.k-dialog-override-section {
    margin-top: 16px;
}
.k-dialog-override-label {
    font-size: 12px;
    font-weight: 700;
    color: #4F5766;
    margin-bottom: 2px;
}

/* Operating-hours weekly editor (one row per weekday). */
.k-dialog-hours-grid {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 4px 0;
}
.k-dialog-hours-row {
    display: grid;
    grid-template-columns: 140px 1fr;
    grid-template-areas:
        "day  times"
        ".    error";
    align-items: center;
    gap: 6px 12px;
}
.k-dialog-hours-day {
    grid-area: day;
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    font-weight: 700;
    color: #4F5766;
    cursor: pointer;
    user-select: none;
}
.k-dialog-hours-times {
    grid-area: times;
    display: flex;
    align-items: center;
    gap: 8px;
}
/* The dialog-wide `.k-dialog .kiq-textbox { width: 100% !important }` rule
   would otherwise stretch the first time input to fill the row and push the
   end-time input out of the modal. Constraining via min/max-width (which has
   the same specificity but doesn't need !important to win the cascade against
   `width: 100% !important` — width is clamped by max-width) keeps both inputs
   visible without an !important war. 140px gives the native clock-picker
   chrome enough room across Chrome/Safari/Firefox. */
.k-dialog .k-dialog-hours-times input[type="time"] {
    flex: 0 0 auto;
    min-width: 140px;
    max-width: 140px;
}
.k-dialog .k-dialog-hours-times input[type="time"]:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.k-dialog-hours-sep {
    color: #4F5766;
}
.k-dialog-hours-error {
    grid-area: error;
}

/* Duration picker utility (Phase 12 task #17). */
.k-duration-picker {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.k-duration-label {
    font-size: 12px;
    font-weight: 700;
    color: #4F5766;
}

/* Scrollable list body for long forms (mapping / settings / similar).
   Flex-fills the available dialog body height so the parent `.k-dialog-body`
   can scroll naturally when content overflows; an inner max-height keeps it
   in check on very tall viewports. The old .k-dialog-mapping-body /
   .k-dialog-settings-body class names are kept as aliases so existing dialog
   markup keeps working — prefer .k-dialog-list-body for new code. */
.k-dialog-list-body,
.k-dialog-mapping-body,
.k-dialog-settings-body {
    padding: 0 0 16px 0;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}
.k-dialog-scroll {
    max-height: min(60vh, 480px);
    overflow-y: auto;
    border: 1px solid #CDD3D9;
}
.k-mapping-form {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding-right: 4px;
}
/* KEN-360 §13.8: shared Mapping card primitive — the §9 card pattern with a
   tighter outer margin. Mirrors the desktop <Border Margin="5 5 22 5"
   CornerRadius="14" BoxShadow="0 0 10 1 #D2D2D2"/> on MappingSourceEditView,
   reusing the shared --kiq-card-* tokens so the radius, padding, shadow, and
   title typography stay in lockstep with the §9 primitive (KCard). */
.k-mapping-source-card {
    background: #ffffff;
    border-radius: var(--kiq-card-radius);
    margin: 5px 22px 5px 5px;
    padding: var(--kiq-card-padding);
    box-shadow: var(--kiq-card-shadow);
}
.k-mapping-source-header {
    display: flex;
    align-items: center;
    gap: 5px;
    margin: 0 0 var(--kiq-card-title-gap);
}
.k-mapping-source-name {
    font-size: var(--kiq-title-font-size);
    font-weight: var(--kiq-title-font-weight);
    color: var(--kiq-title-color);
}
/* §13.8 auto action row inside a mapping source card — same 11 px button gap
   and 28 px (sometimes 18 px) separation from the content above as the §9
   card variant. */
.k-mapping-source-buttons {
    display: flex;
    gap: var(--kiq-button-gap);
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    margin-top: var(--kiq-card-button-gap);
}
.k-mapping-source-buttons--tight { margin-top: var(--kiq-card-button-gap-tight); }
/* Mapping rows reuse the shared ListBox.parameters chrome: both the edit-side
   MappingSourceCard and the read-only MappingSourceReadOnly host their items
   inside .k-param-list, so each row is a 240 px slot with a 28 px right pad
   (.k-parameter or .k-parameter-label) and the typography matches every other
   ListBox.parameters wrap in the app. */
/* Vertical layout inside DialogSettingsCreate / DialogSettingsEdit:
   one parameter per row, full-width input, comfortable spacing. */
.k-settings-form {
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 16px;
    padding-top: 4px;
}
.k-settings-form > .k-parameter {
    flex: 0 0 auto;
    width: 100%;
    min-width: 0;
    padding: 0;
    border-right: none;
}
.k-settings-form > .k-parameter:first-child { padding-left: 0; }
.k-settings-form > .k-parameter:last-child  { padding-right: 0; }

.k-settings-form > .k-parameter .k-parameter-editor,
.k-settings-form > .k-parameter .k-parameter-editor > *,
.k-settings-form > .k-parameter .k-parameter-editor input,
.k-settings-form > .k-parameter .k-parameter-editor select,
.k-settings-form > .k-parameter .k-parameter-combobox {
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
}

/* Inside any dialog, form inputs span the full available width.
   Scoped via .k-dialog so the same controls keep their compact 176 px
   sizing on main pages (Status grids on Thermostat / Sensor / *Repository
   pages etc.). !important is required only to defeat the inline
   style="width:{N}px" used by KSearchCombobox + ParameterString*. */
.k-dialog .k-parameter { min-width: 0; }
.k-dialog .k-parameter-editor,
.k-dialog .k-parameter-editor > * {
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
}
.k-dialog .kiq-textbox,
.k-dialog .k-parameter-combobox,
.k-dialog .k-parameter-filter-wrap,
.k-dialog .kiq-numeric-units,
.k-dialog .kiq-search-combo-wrap,
.k-dialog .kiq-search-combo-wrap .rz-dropdown,
.k-dialog .kiq-search-combo {
    width: 100% !important;
    max-width: 100%;
    box-sizing: border-box;
}
.k-dialog .kiq-numeric-units input {
    flex: 1 1 auto;
    width: 100%;
    text-align: left;
    padding: 0 8px;
}
.k-dialog .k-parameter-timerange { width: 100%; }
.k-dialog .k-parameter-timerange select { flex: 1 1 0; width: auto; }

/* Override-dialog sections — section labels above inputs, generous
   vertical rhythm, no double margin at the top of the body. */
.k-dialog-override-section:first-child { margin-top: 0; }
.k-dialog .k-dialog-override-section select,
.k-dialog .k-dialog-override-section input,
.k-dialog .k-dialog-override-section .kiq-textbox {
    width: 100% !important;
    max-width: 100%;
    box-sizing: border-box;
}

/* Compact dialog rhythm — desktop XAML uses 22 px header / 16 px gaps which
   read as airy on the web. Tighten label↔input and row↔row spacing only when
   the parameter chrome is rendered inside a .k-dialog; main-page Status grids
   keep the desktop-mirroring sizing. */
.k-dialog .k-dialog-content { padding-top: 14px; }
.k-dialog .k-dialog-header { padding-bottom: 16px; }
/* DialogShell wraps body in .k-dialog-message which uses line-height: 1.5 +
   white-space: pre-wrap for plain-text confirm dialogs. Reset white-space for
   parameter forms (otherwise indentation renders as visible whitespace) but
   keep a comfortable line-height for label legibility. */
.k-dialog .k-dialog-message:has(.k-settings-form),
.k-dialog .k-dialog-message:has(.k-mapping-form),
.k-dialog .k-dialog-message:has(.k-dialog-override-section) {
    line-height: 1.4;
    white-space: normal;
}
.k-dialog .k-parameter-header {
    height: auto;
    min-height: 0;
    margin-bottom: 6px;
    line-height: 1.4;
}
.k-dialog .k-parameter-name { line-height: 1.4; }
/* SettingsForm carries both .k-param-list and .k-settings-form. The former is
   a wrap-row layout for Status grids and ships an extra 14 px margin-top + a
   wide gap; reset both inside dialogs so only .k-settings-form's column rhythm
   applies. */
.k-dialog .k-param-list.k-settings-form { margin: 0; }
.k-dialog .k-settings-form { gap: 14px; padding-top: 0; }
.k-dialog .k-dialog-override-section { margin-top: 14px; }
.k-dialog .k-dialog-override-section:first-child { margin-top: 0; }

/* Tablet / small-laptop viewport: shrink the wrapper safe-area so dialogs
   reclaim the extra width. The k-size-* widths use `min(calc(100vw - 32px),
   …)` at desktop; here we re-declare them with the matching 24px safe-area
   so the fluid term tracks the wrapper padding (12px × 2). */
@media (max-width: 900px) {
    .rz-dialog-wrapper:has(.k-dialog) { padding: 12px; }
    .k-dialog { max-width: calc(100vw - 24px); max-height: calc(100vh - 24px); }
    .k-dialog.k-size-compact { width: min(calc(100vw - 24px), 320px); }
    .k-dialog.k-size-default { width: min(calc(100vw - 24px), 420px); }
    .k-dialog.k-size-wide    { width: min(calc(100vw - 24px), 1060px); }
    .k-dialog.k-size-full    { width: min(calc(100vw - 24px), 1240px); }
}

/* Phone-sized viewport: tighten padding, stack footer buttons full-width with
   primary on top (column-reverse + primary rendered last), drop title size,
   collapse safe-area to the minimum so the dialog has room to breathe. */
@media (max-width: 480px) {
    :root { --k-dialog-pad-x: 14px; }
    .rz-dialog-wrapper:has(.k-dialog) { padding: 8px; }
    .k-dialog { max-width: calc(100vw - 16px); max-height: calc(100vh - 16px); }
    .k-dialog.k-size-compact,
    .k-dialog.k-size-default,
    .k-dialog.k-size-wide,
    .k-dialog.k-size-full { width: calc(100vw - 16px); }
    .k-dialog-content { padding-top: 12px; padding-bottom: 10px; }
    .k-dialog-header { gap: 8px; padding-bottom: 6px; }
    .k-dialog-title { font-size: 15px; }
    .k-dialog-footer {
        padding: 8px var(--k-dialog-pad-x);
        flex-direction: column-reverse;
        align-items: stretch;
    }
    .k-dialog-footer-left,
    .k-dialog-footer-right {
        width: 100%;
        flex-direction: column-reverse;
        align-items: stretch;
        gap: 6px;
    }
    .k-dialog-footer-right { margin-left: 0; }
    .k-dialog-footer .kiq-button { width: 100%; flex: 1 1 100%; min-width: 0; }
    .k-dialog-scroll { max-height: 50vh; }
    .k-dialog .k-settings-form { gap: 12px; }
    .k-dialog .k-dialog-override-section { margin-top: 12px; }
}

/* DeviceObjectPropertiesDialog grid — mirrors desktop's alternating-row #F7F7F7. */
.k-properties-dialog .rz-data-grid .rz-grid-table tbody tr:nth-child(even) td {
    background: #F7F7F7;
}
/* KEN-379 §13.7: DeviceObjectPropertiesView Close button is exactly 100 px wide,
   right-aligned. The dialog's 3-row layout (title / grid / footer) and the
   missing header icon are handled in DeviceObjectPropertiesDialog.razor. */
.k-dialog:has(.k-properties-dialog) .k-dialog-footer-right > .kiq-button {
    min-width: 100px;
    width: 100px;
}

/* ThermostatRepositoryView Overview grid — DataGridRow:nth-child(even) #F7F7F7. */
.k-thermostat-repo-grid .rz-grid-table tbody tr:nth-child(even) td {
    background: #F7F7F7;
}

/* VavRepositoryView Overview grid — DataGridRow:nth-child(even) #F7F7F7. */
.k-vav-repo-grid .rz-grid-table tbody tr:nth-child(even) td {
    background: #F7F7F7;
}

/* ---------- Busy overlay (Phase 16) — mirrors desktop ShowLoading dialog ---------- */
/* Scrim color/opacity per MainWindow.axaml: Background="#595959" Opacity="0.3". */
/* KEN-364: z-index sits above page content but below modal dialogs — Radzen dialog
   wrapper is 1001 (--rz-dialog-zindex) and ReconnectModal is 9998, so 1000 keeps
   busy below both per §5.2 ("above splash but below modal dialogs"). */
.k-busy-overlay {
    position: fixed;
    inset: 0;
    background: rgba(89, 89, 89, 0.3);
    z-index: 1000;
    display: flex;
    justify-content: center;
    align-items: center;
}
.k-busy-card {
    display: flex;
    align-items: center;
    gap: 12px;
    background: #ffffff;
    padding: 16px 20px;
    border-radius: 4px;
    box-shadow: var(--m3-elevation-3);
    min-width: 220px;
    max-width: 480px;
}
.k-busy-text {
    font-size: 13px;
    color: #1c1c1c;
}
.k-busy-progress {
    flex: 1;
    min-width: 80px;
    height: 6px;
}
.k-busy-spin {
    display: inline-block;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    background-image: url("../icons/processing_1_x32.png");
    background-repeat: no-repeat;
    background-size: 32px 32px;
    background-position: center;
    /* §4.4: 4 frames × 250 ms = 1 s cycle, steps(1, end) so each keyframe
       holds its image until the next keyframe boundary. */
    animation: k-busy-spin-frames var(--kiq-spinner-cycle) steps(1, end) infinite;
}
@keyframes k-busy-spin-frames {
    0%   { background-image: url("../icons/processing_1_x32.png"); }
    25%  { background-image: url("../icons/processing_2_x32.png"); }
    50%  { background-image: url("../icons/processing_3_x32.png"); }
    75%  { background-image: url("../icons/processing_4_x32.png"); }
    100% { background-image: url("../icons/processing_1_x32.png"); }
}

/* §4.4 4-frame progress spinner (mirrors desktop processing_1..4_x32.png).
   250 ms per frame × 4 frames = 1 s full cycle. */
.k-spinner {
    display: inline-block;
    width: 32px;
    height: 32px;
    background-image: url("../icons/processing_1_x32.png");
    background-repeat: no-repeat;
    background-size: 32px 32px;
    background-position: center;
    animation: k-spin var(--kiq-spinner-cycle) steps(4) infinite;
}
@keyframes k-spin {
    0%   { background-image: url("../icons/processing_1_x32.png"); }
    25%  { background-image: url("../icons/processing_2_x32.png"); }
    50%  { background-image: url("../icons/processing_3_x32.png"); }
    75%  { background-image: url("../icons/processing_4_x32.png"); }
    100% { background-image: url("../icons/processing_1_x32.png"); }
}

/* ---------- Text overflow fade (mirror of TextOverflowLinearGradientBrush) ---------- */
.kiq-text-fade {
    -webkit-mask-image: linear-gradient(to right, black 80%, transparent);
    mask-image: linear-gradient(to right, black 80%, transparent);
    white-space: nowrap;
    overflow: hidden;
}

/* ---------- Context menu (KenergyContextMenu) ---------- */
.kiq-context-menu {
    display: flex;
    flex-direction: column;
    min-width: 160px;
    padding: 4px 0;
    background: #ffffff;
    border: 1px solid #cccccc;
    border-radius: 4px;
    box-shadow: var(--m3-elevation-2);
    font-family: inherit;
    font-size: 12px;
    color: #222;
}
.kiq-context-menu-item {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 28px;
    padding: 0 12px;
    border: 0;
    background: transparent;
    text-align: left;
    cursor: pointer;
    color: inherit;
    font: inherit;
}
.kiq-context-menu-item:hover:not(:disabled),
.kiq-context-menu-item:focus:not(:disabled) {
    background: var(--kiq-hover);
    outline: none;
}
.kiq-context-menu-item:disabled {
    color: var(--kiq-basic-gray);
    cursor: default;
}
.kiq-context-menu-icon {
    flex: 0 0 16px;
    width: 16px;
    height: 16px;
}
.kiq-context-menu-item:disabled .kiq-context-menu-icon {
    opacity: 0.5;
}
.kiq-context-menu-label {
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Building Import dialog (mirror of BuildingImportDialogView.axaml). */
.k-import-dialog { display: flex; flex-direction: column; font-family: inherit; font-size: 12px; color: #4F5766; }
.k-import-banner {
    background: #FFF8E1;
    border: 1px solid #E1B45A;
    border-radius: 3px;
    padding: 8px 12px;
    font-size: 12px;
    margin: 8px 0 12px;
}
.k-import-counters {
    display: flex; gap: 16px; align-items: center; margin: 8px 0;
}
.k-import-row {
    border: 1px solid #D2D2D2;
    border-radius: 6px;
    padding: 12px;
    margin-bottom: 8px;
}
.k-import-row-header { display: flex; align-items: center; gap: 8px; }
.k-import-row-label { font-size: 13px; font-weight: 600; }
.k-import-action-chip {
    border-radius: 3px;
    padding: 2px 8px;
    font-size: 11px;
    font-weight: 700;
    background: #EEF2FF;
    color: #4F5766;
}
.k-import-action-chip.k-chip-create,
.k-import-action-chip.k-chip-update { background: #EEF2FF; color: #4F5766; }
.k-import-validation-error {
    color: #B00020;
    font-size: 11px;
    margin: 4px 0 0 24px;
}
.k-import-fields { margin: 6px 0 0 24px; display: flex; flex-direction: column; gap: 4px; }
.k-import-field { font-size: 11px; }
.k-import-field-header { display: flex; align-items: center; gap: 4px; }
.k-import-field-name { font-weight: 600; }
.k-import-fold {
    background: none; border: none; padding: 0; cursor: pointer;
}
.k-import-old-summary { color: #B00020; font-family: monospace; display: inline; margin-right: 8px; }
.k-import-new-summary { color: #1B5E20; font-family: monospace; display: inline; }
.k-import-apply-selected { min-width: 120px; margin-left: 11px; }
.k-import-top-spacer { height: 24px; }
.k-import-old-json {
    color: #B00020;
    font-family: monospace;
    background: #FBEAEA;
    padding: 4px 6px;
    margin: 2px 0;
    white-space: pre-wrap;
}
.k-import-new-json {
    color: #1B5E20;
    font-family: monospace;
    background: #EAF6EC;
    padding: 4px 6px;
    margin: 2px 0;
    white-space: pre-wrap;
}

/* Apply-result panel — web-only addition over the desktop dialog. */
.k-import-result-row {
    display: flex; gap: 8px; align-items: center;
    padding: 6px 12px;
    border-bottom: 1px solid #E5E8EC;
    font-size: 12px;
}
.k-import-result-status {
    border-radius: 3px;
    padding: 2px 8px;
    font-weight: 600;
    font-size: 11px;
    text-transform: lowercase;
}
.k-import-result-status.k-status-applied { background: #E8F5E9; color: #1B5E20; }
.k-import-result-status.k-status-skipped { background: #FFF8E1; color: #6D5400; }
.k-import-result-status.k-status-failed { background: #FBEAEA; color: #B00020; }
.k-import-result-label { flex: 1; }
.k-import-result-error { color: #B00020; font-size: 11px; }

/* ---------- Sign-in page (KEN-325) ---------- */
.k-login-shell {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    background: #F4F6F8;
}
.k-login-card {
    display: flex;
    flex-direction: column;
    gap: 12px;
    width: 320px;
    padding: 32px;
    background: #ffffff;
    border: 1px solid #E5E5E5;
    border-radius: 8px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
.k-login-title { margin: 0; font-size: 22px; color: #016FE6; text-align: center; }
.k-login-sub { margin: 0 0 8px; font-size: 13px; color: #4F5766; text-align: center; }
.k-login-error {
    margin: 0;
    padding: 8px 10px;
    font-size: 12px;
    color: #B00020;
    background: #FBEAEA;
    border-radius: 4px;
}
.k-login-ok {
    margin: 0;
    padding: 8px 10px;
    font-size: 12px;
    color: #1E7A34;
    background: #E8F5EC;
    border-radius: 4px;
}
.k-login-input {
    height: 36px;
    padding: 0 10px;
    font-size: 14px;
    border: 1px solid #D0D5DD;
    border-radius: 4px;
}
.k-login-input:focus { outline: none; border-color: #016FE6; }
.k-login-btn {
    height: 38px;
    margin-top: 4px;
    font-size: 14px;
    color: #ffffff;
    background: #016FE6;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}
.k-login-btn:hover { background: #0159b8; }
.k-login-alt { margin: 4px 0 0; font-size: 12px; color: #4F5766; text-align: center; }
.k-login-alt a { color: #016FE6; text-decoration: none; }
.k-login-alt a:hover { text-decoration: underline; }

/* Static branded sidebar shown on the sign-in / registration pages so the
   left side bar stays visible for unauthenticated users (KEN-326). The live
   NavTree can't render without an authenticated gRPC circuit. */
.k-login-sidebar {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 24px 20px;
    background: #ffffff;
}
.k-login-brand { font-size: 20px; font-weight: 600; color: #016FE6; }
.k-login-brand-sub { margin: 0; font-size: 12px; color: #4F5766; }

/* ── Home page (KEN-454) ──────────────────────────────────────────────── */
.k-home-page { padding: 16px 20px; }
.k-home-header { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.k-home-title { font-size: 16px; font-weight: 600; color: var(--kiq-text-primary); flex: 1; }
.k-home-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48px 0; gap: 8px; }
.k-home-empty-icon { margin-bottom: 4px; }
.k-home-empty-text { font-size: 14px; font-weight: 500; color: var(--kiq-text-primary); }
.k-home-empty-sub { font-size: 12px; color: #6b7280; }
.k-home-empty-action { margin-top: 8px; }
.k-home-buildings { display: flex; flex-wrap: wrap; gap: 12px; }
.k-home-building-card { min-width: 220px; max-width: 320px; transition: box-shadow 0.15s; }
.k-home-building-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.12); }
.k-home-building-id { font-size: 11px; color: #6b7280; margin-top: 4px; font-family: monospace; }


