@import '_content/FT.Web.Shared/FT.Web.Shared.9c1cuhuigd.bundle.scp.css';

/* /Components/Layout/NavMenu.razor.rz.scp.css */
/* NavMenu scoped styles — MudBlazor handles all nav styling */
/* File giữ trống — tránh conflict với MudNavMenu */
/* /Components/Layout/ReconnectModal.razor.rz.scp.css */
.components-reconnect-first-attempt-visible[b-cikjptsonh],
.components-reconnect-repeated-attempt-visible[b-cikjptsonh],
.components-reconnect-failed-visible[b-cikjptsonh],
.components-pause-visible[b-cikjptsonh],
.components-resume-failed-visible[b-cikjptsonh],
.components-rejoining-animation[b-cikjptsonh] {
    display: none;
}

#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible[b-cikjptsonh],
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation[b-cikjptsonh],
#components-reconnect-modal.components-reconnect-paused .components-pause-visible[b-cikjptsonh],
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible[b-cikjptsonh],
#components-reconnect-modal.components-reconnect-retrying[b-cikjptsonh],
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible[b-cikjptsonh],
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation[b-cikjptsonh],
#components-reconnect-modal.components-reconnect-failed[b-cikjptsonh],
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible[b-cikjptsonh] {
    display: block;
}


#components-reconnect-modal[b-cikjptsonh] {
    background-color: white;
    width: 20rem;
    margin: 20vh auto;
    padding: 2rem;
    border: 0;
    border-radius: 0.5rem;
    box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
    opacity: 0;
    transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
    animation: components-reconnect-modal-fadeOutOpacity-b-cikjptsonh 0.5s both;
    &[open]

{
    animation: components-reconnect-modal-slideUp-b-cikjptsonh 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity-b-cikjptsonh 0.5s ease-in-out 0.3s;
    animation-fill-mode: both;
}

}

#components-reconnect-modal[b-cikjptsonh]::backdrop {
    background-color: rgba(0, 0, 0, 0.4);
    animation: components-reconnect-modal-fadeInOpacity-b-cikjptsonh 0.5s ease-in-out;
    opacity: 1;
}

@keyframes components-reconnect-modal-slideUp-b-cikjptsonh {
    0% {
        transform: translateY(30px) scale(0.95);
    }

    100% {
        transform: translateY(0);
    }
}

@keyframes components-reconnect-modal-fadeInOpacity-b-cikjptsonh {
    0% {
        opacity: 0;
    }

    100% {
        opacity: 1;
    }
}

@keyframes components-reconnect-modal-fadeOutOpacity-b-cikjptsonh {
    0% {
        opacity: 1;
    }

    100% {
        opacity: 0;
    }
}

.components-reconnect-container[b-cikjptsonh] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
}

#components-reconnect-modal p[b-cikjptsonh] {
    margin: 0;
    text-align: center;
}

#components-reconnect-modal button[b-cikjptsonh] {
    border: 0;
    background-color: #6b9ed2;
    color: white;
    padding: 4px 24px;
    border-radius: 4px;
}

    #components-reconnect-modal button:hover[b-cikjptsonh] {
        background-color: #3b6ea2;
    }

    #components-reconnect-modal button:active[b-cikjptsonh] {
        background-color: #6b9ed2;
    }

.components-rejoining-animation[b-cikjptsonh] {
    position: relative;
    width: 80px;
    height: 80px;
}

    .components-rejoining-animation div[b-cikjptsonh] {
        position: absolute;
        border: 3px solid #0087ff;
        opacity: 1;
        border-radius: 50%;
        animation: components-rejoining-animation-b-cikjptsonh 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
    }

        .components-rejoining-animation div:nth-child(2)[b-cikjptsonh] {
            animation-delay: -0.5s;
        }

@keyframes components-rejoining-animation-b-cikjptsonh {
    0% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    4.9% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    5% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 1;
    }

    100% {
        top: 0px;
        left: 0px;
        width: 80px;
        height: 80px;
        opacity: 0;
    }
}
/* /Components/Pages/Admin/ReportSettingsPage.razor.rz.scp.css */
/* Fix: BadgeData trên MudTabPanel bị cắt bởi overflow:hidden của MudTabs toolbar.
   Dùng ::deep để pierce vào MudBlazor internal markup từ scoped CSS.
   + thêm padding-right vào tab để badge có chỗ position absolute không tràn header. */

[b-zti1243kcs] .mud-tabs-toolbar {
    overflow: visible !important;
}

[b-zti1243kcs] .mud-tabs-tabbar-inner {
    overflow: visible !important;
}

[b-zti1243kcs] .mud-tab {
    overflow: visible !important;
    padding-right: 28px;
}

/* Tab Thông báo — một bảng, hàng tiêu đề nhóm theo module (Responsive-Layout-Standard §6.7).
   th / td / tr do CHÍNH trang này viết (ChildContent của MudSimpleTable) nên mang scope của trang — không cần ::deep.
   Tiêu đề, mã và cụm chip không xuống dòng: cột width:1% co sát nội dung, chỉ "Tên hiển thị" giãn + xuống dòng. */
.notif-table th[b-zti1243kcs],
.notif-table .notif-code[b-zti1243kcs],
.notif-table .notif-chips[b-zti1243kcs] {
    white-space: nowrap;
}

.notif-table .notif-group td[b-zti1243kcs] {
    background: var(--mud-palette-background-gray);
    color: var(--mud-palette-primary);
    font-weight: 600;
}

[b-zti1243kcs] .mud-tab .mud-badge-wrapper,
[b-zti1243kcs] .mud-tab .mud-badge {
    overflow: visible !important;
}
/* /Components/Pages/Energy/EnergyCostDashboardPage.razor.rz.scp.css */
/* ══════════════════════════════════════════════════════════════
   Energy Cost Dashboard — Premium Dark-Friendly Design
   All colors use MudBlazor CSS variables for dark mode compat
   ══════════════════════════════════════════════════════════════ */

/* ── Page Header ── */
.cost-dashboard-header[b-kakkyf2piv] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 24px;
}
.cost-dashboard-title[b-kakkyf2piv] {
    display: flex;
    align-items: center;
    gap: 10px;
}
.cost-dashboard-title-text[b-kakkyf2piv] {
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--mud-palette-text-primary);
}
.cost-dashboard-subtitle[b-kakkyf2piv] {
    font-size: 0.85rem;
    color: var(--mud-palette-text-secondary);
}

/* ── KPI Cards Grid ── */
.kpi-grid[b-kakkyf2piv] {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 16px;
    margin-bottom: 24px;
}
@@media (max-width: 1200px) { .kpi-grid[b-kakkyf2piv] { grid-template-columns: repeat(3, 1fr); } }
@@media (max-width: 768px) { .kpi-grid[b-kakkyf2piv] { grid-template-columns: repeat(2, 1fr); } }

.kpi-card[b-kakkyf2piv] {
    position: relative;
    border-radius: 12px;
    padding: 20px;
    overflow: hidden;
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.kpi-card:hover[b-kakkyf2piv] {
    transform: translateY(-3px);
    box-shadow: 0 8px 24px rgba(0,0,0,0.18);
}
.kpi-card[b-kakkyf2piv]::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
}
.kpi-card.kpi-today[b-kakkyf2piv]::before    { background: linear-gradient(90deg, #FF9800, #FFB74D); }
.kpi-card.kpi-mtd[b-kakkyf2piv]::before      { background: linear-gradient(90deg, #2196F3, #64B5F6); }
.kpi-card.kpi-kwh[b-kakkyf2piv]::before      { background: linear-gradient(90deg, #4CAF50, #81C784); }
.kpi-card.kpi-avg[b-kakkyf2piv]::before      { background: linear-gradient(90deg, #9C27B0, #CE93D8); }
.kpi-card.kpi-budget[b-kakkyf2piv]::before   { background: linear-gradient(90deg, #00BCD4, #4DD0E1); }

.kpi-icon-wrap[b-kakkyf2piv] {
    width: 44px;
    height: 44px;
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 12px;
}
.kpi-today .kpi-icon-wrap[b-kakkyf2piv]    { background: rgba(255,152,0,0.12); }
.kpi-mtd .kpi-icon-wrap[b-kakkyf2piv]      { background: rgba(33,150,243,0.12); }
.kpi-kwh .kpi-icon-wrap[b-kakkyf2piv]      { background: rgba(76,175,80,0.12); }
.kpi-avg .kpi-icon-wrap[b-kakkyf2piv]      { background: rgba(156,39,176,0.12); }
.kpi-budget .kpi-icon-wrap[b-kakkyf2piv]   { background: rgba(0,188,212,0.12); }

.kpi-value[b-kakkyf2piv] {
    font-size: 1.75rem;
    font-weight: 800;
    font-family: 'Roboto Mono', 'Consolas', monospace;
    color: var(--mud-palette-text-primary);
    line-height: 1.2;
    margin-bottom: 4px;
}
.kpi-label[b-kakkyf2piv] {
    font-size: 0.78rem;
    font-weight: 500;
    color: var(--mud-palette-text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.kpi-sub[b-kakkyf2piv] {
    font-size: 0.72rem;
    color: var(--mud-palette-text-disabled);
    margin-top: 6px;
}

/* ── Chart Panels ── */
.charts-row[b-kakkyf2piv] {
    display: grid;
    grid-template-columns: 1fr 1.5fr;
    gap: 16px;
    margin-bottom: 24px;
}
@@media (max-width: 960px) { .charts-row[b-kakkyf2piv] { grid-template-columns: 1fr; } }

.chart-panel[b-kakkyf2piv] {
    border-radius: 12px;
    padding: 20px 24px;
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
}
.chart-panel-title[b-kakkyf2piv] {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--mud-palette-text-primary);
    margin-bottom: 16px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

/* ── Budget Section ── */
.budget-panel[b-kakkyf2piv] {
    border-radius: 12px;
    padding: 20px 24px;
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
}

.budget-summary-grid[b-kakkyf2piv] {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 12px;
    margin-top: 16px;
}
@@media (max-width: 768px) { .budget-summary-grid[b-kakkyf2piv] { grid-template-columns: repeat(2, 1fr); } }

.budget-summary-item[b-kakkyf2piv] {
    text-align: center;
    padding: 14px 8px;
    border-radius: 10px;
    background: var(--mud-palette-background);
    border: 1px solid var(--mud-palette-lines-default);
}
.budget-summary-value[b-kakkyf2piv] {
    font-size: 1.1rem;
    font-weight: 700;
    font-family: 'Roboto Mono', monospace;
    color: var(--mud-palette-text-primary);
}
.budget-summary-label[b-kakkyf2piv] {
    font-size: 0.72rem;
    color: var(--mud-palette-text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-top: 4px;
}

/* ── Empty State ── */
.empty-state[b-kakkyf2piv] {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 48px 24px;
    gap: 8px;
}
/* /Components/Pages/Energy/EnergyDashboardPage.razor.rz.scp.css */
/* ── Energy Dashboard — Fixed viewport + Fixed card size ──
   Same pattern as MachineDashboard: flex column fills main-content,
   fixed card 380×260, JS ResizeObserver → pageSize.
*/

.dashboard-root[b-kcqp9jqz42] {
    height: calc(100vh - 64px); /* AppBar: 64px */
    display: flex;
    flex-direction: column;
    padding: 8px 12px;
    overflow: hidden;
    gap: 8px;
}

.dashboard-summary[b-kcqp9jqz42] {
    flex: 0 0 auto;
    margin-bottom: 0 !important;
}

.dashboard-root[b-kcqp9jqz42]  .dashboard-toolbar,
.dashboard-root > .mud-alert[b-kcqp9jqz42],
.dashboard-root > .mud-progress-linear[b-kcqp9jqz42] {
    flex: 0 0 auto;
}

/* ── Device grid: fixed card size, center-aligned ── */
.energy-device-grid[b-kcqp9jqz42] {
    /* Chuẩn theo Machine: bề rộng card = --card-w (400px) — đồng bộ 3 dashboard (Đợt 2026-06-10).
       2026-10-02: đọc BIẾN CHUNG --ft-energy-card-w (app.css, 400px) như lưới Máy đọc --ft-machine-card-w — bậc bề rộng
       thẻ của cấu hình trang đặt lại biến này trên .dashboard-root, và khung xem trước của tab Cấu hình dashboard đọc
       cùng biến. Gõ số ở đây là hai nơi lệch nhau và xem trước nói dối (bài học Máy 2026-08-15). */
    --card-w: var(--ft-energy-card-w, 400px);
    flex: 1 1 auto;
    display: grid;
    /* Responsive (mobile): màn ≥W giữ cột cố định W (desktop Y NGUYÊN),
       màn <W cột co về 100% (full-width, hết cắt cụt). KHÔNG cần @media. */
    grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--card-w)), var(--card-w)));
    grid-auto-rows: max-content;
    gap: 12px;
    justify-content: center;
    align-content: start;
    overflow: hidden;
    min-height: 0;
}

/* Cột dọc (display:flex) nay ở DashboardCard.razor.css (2026-10-05). */
.energy-device-grid[b-kcqp9jqz42]  .device-card {
    height: 100%;
    overflow: hidden;
}
/* /Components/Pages/Generic/DeviceProfileForm.razor.rz.scp.css */
/* Hai bảng của form (chỉ số 10 cột + điểm điều khiển 7 cột) nằm trong vùng thuộc tính hẹp.
   Lề ô mặc định của MudSimpleTable Dense là 6px 16px, tức 32px BỀ NGANG mỗi ô — 10 cột
   là ~320px chỉ để trống, đủ đẩy bảng sang cuộn ngang. Bóp còn 12px/ô.

   ⚠️ Bậc chọn lọc phải THẮNG rule gốc `.mud-simple-table.mud-table-dense * tr td{padding:6px 16px}`
   (2 class + 2 element). Ở đây 2 class + 2 element + thuộc tính scope Blazor tự thêm vào `td`
   → hơn đúng một bậc. Hạ xuống `td { }` trơn là "rule nạp mà không ăn".

   KHÔNG đụng chiều cao ô nhập (Margin.Dense = 40px): đó là chuẩn vùng chạm
   (Responsive-Layout-Standard §8.2, tối thiểu 36px trên màn HMI/tablet). */
.mud-simple-table.mud-table-dense tr td[b-i3x4v9wx9u],
.mud-simple-table.mud-table-dense tr th[b-i3x4v9wx9u] {
    padding: 3px 6px;
}

/* Khối danh tính đóng khung, tách hẳn khỏi vùng tab — trước đó hai phần trôi liền nhau nên
   nhìn không ra đâu là danh tính profile, đâu là nội dung của tab đang mở.
   Bo góc hợp lệ vì viền ĐỦ 4 CẠNH (luật repo: viền hở cạnh thì không bo góc). */
.prof-identity[b-i3x4v9wx9u] {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
    padding: 8px 12px;
    margin-bottom: 12px;
}

/* Chống NHẢY chiều cao khi đổi tab. Ba tab cao khác nhau rất xa (profile 12 chỉ số: tab Chỉ số
   ~630px, tab Điểm điều khiển ~170px) nên `min-height` như TaskDetailDialog chỉ giảm chứ không
   hết — phải CỐ ĐỊNH chiều cao vùng panel rồi cho cuộn bên trong. Được thêm: tab Chỉ số không
   còn vươn dài vô tận theo số dòng.
   ::deep vì .mud-tabs-panels do MudTabs render, CSS scoped không chạm thẳng tới được. */
.prof-tabs[b-i3x4v9wx9u]  .mud-tabs-panels {
    height: 420px;
    overflow-y: auto;
}

/* Màn hẹp: trả về tự nhiên — hộp cuộn lồng trong trang đang cuộn là rất khó dùng trên điện thoại
   (mirror cách TaskDetailDialog bỏ height cứng ở mobile). */
@media (max-width: 600px) {
    .prof-tabs[b-i3x4v9wx9u]  .mud-tabs-panels {
        height: auto;
        overflow-y: visible;
    }
}

/* Ô "Màu" của bảng quy tắc (tab DASHBOARD) nay là ScadaColorField (2026-10-04) — component mang CSS riêng (ô vuông xem màu + ô hex);
   bề rộng do <th> của cột quy định. Bẫy cũ vẫn đúng với mọi MudColorPicker: KHÔNG đặt Style/width lên gốc picker (gốc chứa cả thân
   bảng màu — bảng chọn bị ép thành dải dọc, không bấm được), muốn hẹp thì siết ở .mud-input-control hoặc ở cột. */
/* /Components/Pages/Generic/GenericDashboardPage.razor.rz.scp.css */
/* Gốc trang (2026-10-04): chỉ mang nền + biến bề rộng thẻ từ cấu hình trang (style inline do trang đặt). Trang cuộn bình
   thường nên KHÔNG dùng lại .dashboard-root (khung cố định 100vh của Máy / Điện — app.css còn nới MudContainer theo class đó);
   min-height để nền phủ hết phần nhìn thấy khi ít thẻ. */
.generic-dashboard-root[b-zavtmaqimg] {
    min-height: calc(100vh - 64px - 32px); /* AppBar 64px + lề MudContainer */
}

/* Grid card Generic — khuôn chung 3 dashboard: card cố định 400px, auto-fill, căn giữa.
   Responsive-by-construction: ≥400px giữ cột cố định, <400px co full-width.
   2026-10-04: đọc BIẾN CHUNG --ft-generic-card-w (app.css, 400px) như lưới Máy / Điện — bậc bề rộng thẻ của cấu hình trang
   đặt lại biến này trên .generic-dashboard-root, khung xem trước của tab Cấu hình dashboard đọc cùng biến. */
.generic-device-grid[b-zavtmaqimg] {
    --card-w: var(--ft-generic-card-w, 400px);
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--card-w)), var(--card-w)));
    grid-auto-rows: max-content;
    gap: 12px;
    justify-content: center;
    align-content: start;
}

.generic-device-grid[b-zavtmaqimg]  .device-card {
    height: 100%;
}
/* /Components/Pages/Generic/GenericDeviceManagerPage.razor.rz.scp.css */
/* Bố cục 2 cột: danh sách loại thiết bị (trái) + vùng thuộc tính gắn cố định (phải).
   Vùng phải THAY hộp thoại Sửa — xem và sửa đều diễn ra tại chỗ.
   Bề rộng cột phải cố định (kéo co giãn để sau); align-items:start để 2 cột không bị
   kéo cao bằng nhau khi danh sách dài. */
.mgr-split[b-a9rw84vss9] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 728px);
    gap: 12px;
    align-items: start;
}

/* Panel dính theo màn khi cuộn danh sách dài — 88px = chiều cao app bar + lề. */
.mgr-props[b-a9rw84vss9] {
    position: sticky;
    top: 88px;
}

/* Màn hẹp: xếp dọc, panel về sau danh sách và bỏ dính (dính trên màn ngắn che mất nội dung).
   Ngưỡng nâng theo bề rộng panel: panel 728px mà vẫn chia 2 cột ở 1280px thì trừ menu trái
   xong danh sách chỉ còn ~300px — chật hơn cả lúc chưa tách cột. */
@media (max-width: 1439.98px) {
    .mgr-split[b-a9rw84vss9] { grid-template-columns: minmax(0, 1fr); }
    .mgr-props[b-a9rw84vss9] { position: static; }
}

/* Trang theo khuôn .page-fill (2026-10-04): màn desktop đủ cao thì nội dung tab cuộn TRONG khối bọc tab, không cuộn trang ⇒
   panel dính theo mép trên của khung cuộn đó, không còn app bar để chừa 88px (giữ 88 là dính lơ lửng giữa thẻ). Ngưỡng cao
   600px đúng ngưỡng của khuôn — thấp hơn thì trang cuộn lại như cũ, 88px lại đúng. */
@media (min-width: 1440px) and (min-height: 600px) {
    .page-fill .mgr-props[b-a9rw84vss9] { top: 0; }
}

/* Bảng điểm đo nằm TRONG một Profile — thụt vào + đường dẫn hướng bên trái để nhìn phát
   biết đang ở tầng nào. Trước đó hai tầng nói cùng ngôn ngữ hình (tên đậm + chip + dãy nút
   icon) nên rất dễ nhầm, nhất là khi mọi Profile đều mở sẵn.

   ⚠️ CỐ Ý KHÔNG dùng viền trái ĐẬM + nền ở đây: cặp đó đã dành cho `.mgr-row-selected`
   (trạng thái ĐANG CHỌN). Dùng lại cùng ngôn ngữ hình là giết mất dấu hiệu chọn — đúng lỗi
   mà mục này đang đi chữa. Đường ở đây mảnh 1px, màu lines, không nền. */
.mgr-points[b-a9rw84vss9] {
    margin-left: 12px;
    padding-left: 12px;
    border-left: 1px solid var(--mud-palette-lines-default);
}

/* Dòng loại thiết bị đang chọn — viền trái đậm, KHÔNG bo góc (rule repo: bo góc chỉ khi
   có viền đủ 4 cạnh). */
[b-a9rw84vss9] .mgr-row-selected {
    border-left: 3px solid var(--mud-palette-primary);
    border-radius: 0;
    background: var(--mud-palette-action-default-hover);
}
/* /Components/Pages/Machines/MachineDashboardPage.razor.rz.scp.css */
/* ── Machine Dashboard — Fixed viewport + Fixed card size ──
   Layout: flex column fills main-content area (MudContainer max-width/padding
   neutralized in app.css via body:has(.dashboard-root) selector).
   Device grid: fixed card size 380×260. JS ResizeObserver measures grid area
   → computes cols × rows → sets pageSize. Extra cards paginate via rotate.
*/

.dashboard-root[b-hhqy29ms99] {
    height: calc(100vh - 64px); /* AppBar: 64px */
    display: flex;
    flex-direction: column;
    padding: 8px 12px;
    overflow: hidden;
    gap: 8px;
}

.dashboard-summary[b-hhqy29ms99] {
    flex: 0 0 auto;
    margin-bottom: 0 !important;
}

.dashboard-root[b-hhqy29ms99]  .dashboard-toolbar,
.dashboard-root > .mud-alert[b-hhqy29ms99],
.dashboard-root > .mud-progress-linear[b-hhqy29ms99] {
    flex: 0 0 auto;
}

/* ── Device grid: fixed card size, center-aligned ── */
.machine-device-grid[b-hhqy29ms99] {
    /* Tiêu chuẩn cũ (Đợt 2026-06-10): card RỘNG CỐ ĐỊNH --card-w, KHÔNG stretch — căn giữa,
       dư thì thành lề 2 bên (đúng kích thước thiết kế, không bị to khi ẩn menu / màn rộng). Gap 12px.
       Giữ 2 cải tiến tốt: CAO max-content (card khít nội dung, hết trống đáy) + JS tự tính cols/rows.
       JS đọc --card-w cho số cột; cao tự đo .device-card.

       ⚠️ Bề rộng lấy từ BIẾN CHUNG --ft-machine-card-w (app.css) chứ không gõ số ở đây: khung
       "Xem trước" trong tab Cấu hình dashboard phải rộng ĐÚNG BẰNG ô lưới này, hai nơi gõ số rời
       nhau thì sớm muộn lệch và xem trước lại nói dối (đúng lỗi user báo 2026-08-15).
       480px = 400 cũ + 20% (user chốt 2026-08-15: thẻ máy nay mang tới 15 ô cấu hình được, rộng
       hơn thì chữ đỡ cắt). CỐ Ý chỉ đổi dashboard MÁY — Energy/Water/Generic giữ 400px vì nội
       dung thẻ ít hơn hẳn; câu "đồng bộ 3 dashboard" của Đợt 2026-06-10 tới đây hết hiệu lực. */
    --card-w: var(--ft-machine-card-w, 480px);
    flex: 1 1 auto;
    display: grid;
    /* Responsive (mobile): minmax(min(100%, W), W) → màn ≥W giữ cột cố định W (desktop Y NGUYÊN),
       màn <W thì cột co về 100% (full-width, hết tràn/cắt cụt 2 bên). KHÔNG cần @media. */
    grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--card-w)), var(--card-w)));
    grid-auto-rows: max-content;
    gap: 12px;
    justify-content: center;
    align-content: start;
    overflow: hidden;
    min-height: 0; /* flex quirk — allow shrink */
}

/* Card inside slot: fill the 380×260 cell; no internal scroll. Cột dọc (display:flex) nay ở DashboardCard.razor.css —
   bộ vẽ chung dồn khối KPI + hàng nút xuống đáy thẻ (2026-10-05). */
.machine-device-grid[b-hhqy29ms99]  .device-card {
    height: 100%;
    overflow: hidden;
}
/* /Components/Pages/Production/OeeConfigPanel.razor.rz.scp.css */
/* Hàng thống kê "Tình trạng sẵn sàng" (2026-08-28).
   Đây là phần tử do CHÍNH component render (div/span) nên dùng class thường — KHÔNG ::deep
   (::deep chỉ dành cho phần tử do component KHÁC render; đặt nhầm là selector không match). */

.stat-row[b-gty010vag7] {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    padding: 6px 0;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    font-size: .875rem;
}

.stats > .stat-row:last-child[b-gty010vag7] {
    border-bottom: none;
}

.stat-val[b-gty010vag7] {
    font-weight: 600;
    white-space: nowrap;
}

/* Danh sách ca — ca ĐANG chạy tô nền nhạt (cùng ngôn ngữ hình với list chọn của EquipmentTreePage,
   KHÔNG dùng viền-trái-đậm: viền đậm là dấu hiệu "đang chọn", không phải "đang chạy"). */
.shift-row[b-gty010vag7] {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 5px 8px;
    border-radius: 6px;
    font-size: .8125rem;
}

.shift-row + .shift-row[b-gty010vag7] {
    margin-top: 2px;
}

.shift-row.active[b-gty010vag7] {
    background: var(--mud-palette-primary-hover);
}

.shift-name[b-gty010vag7] {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.shift-time[b-gty010vag7] {
    color: var(--mud-palette-text-secondary);
    white-space: nowrap;
}
/* /Components/Pages/Production/OeeExplainDialog.razor.rz.scp.css */
/* Khối 3 dòng dẫn xuất (PPT / Operating Time / Good Pieces) — tách nền để đọc như phần
   "= ... = ..." in đậm giữa 2 nhóm bảng của bản gốc oee.com. */
.derived[b-n3ang9q3nw] {
    background: var(--mud-palette-background-grey);
    border-radius: 6px;
    padding: 10px 12px;
    font-size: .8125rem;
    line-height: 1.9;
}
/* /Components/Pages/Production/OeeReportPage.razor.rz.scp.css */
/* Thẻ KPI đầu trang: cao bằng thẻ cao nhất hàng (MudItem d-flex), xếp cột,
   chú thích dồn đáy để nhãn + số của mọi thẻ cùng một đường ngang.
   ::deep cần một phần tử HTML của chính trang làm tổ tiên — `div.oee-kpis` giữ vai đó (gốc trang là MudContainer). */
[b-eukomzxpr2] .oee-kpi {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    padding: 12px;
    border-radius: 12px;
}

[b-eukomzxpr2] .oee-kpi-caption {
    margin-top: auto;
}
/* /Components/Pages/Reports/ReportCenterPage.razor.rz.scp.css */
/* Tab "In báo cáo": danh sách mẫu (trái) + form tham số (phải). Thay MudGrid xs=12 md=4/8 — lề âm của MudGrid tràn khung cuộn.
   Dưới md: xếp chồng. Từ md: hai cột (1/3 – 2/3, khe 24px). Từ lg + cao ≥ 600 trong trang page-fill: hai cột cuộn độc lập
   (Responsive-Layout-Standard §19 — gốc .rc-print mang page-fill-fit nên cao bằng phần còn lại của vùng tab). */
.rc-print[b-gn6zrv4g1h] {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

@media (min-width: 960px) {
    .rc-print[b-gn6zrv4g1h] {
        flex-direction: row;
        align-items: flex-start;
        gap: 24px;
    }
    .rc-print-list[b-gn6zrv4g1h] {
        flex: 0 0 calc((100% - 24px) / 3);
        min-width: 0;
    }
    .rc-print-form[b-gn6zrv4g1h] {
        flex: 1 1 0;
        min-width: 0;
    }
}

@media (min-width: 1280px) and (min-height: 600px) {
    /* Đệm nhỏ để bóng thẻ không bị khung cuộn cắt. */
    .page-fill .rc-print[b-gn6zrv4g1h] {
        align-items: stretch;
    }
    .page-fill .rc-print-list[b-gn6zrv4g1h],
    .page-fill .rc-print-form[b-gn6zrv4g1h] {
        min-height: 0;
        overflow-y: auto;
        padding: 2px 4px 2px 2px;
    }
}
/* /Components/Pages/Reports/ReportOverlayDialog.razor.rz.scp.css */
/* Khung tab cao cố định, cuộn bên trong (Responsive-Layout-Standard §7.2a): tab Cột có thể vài chục dòng, tab Tiêu đề và trang chỉ
   vài ô — để tự co thì nút Lưu nhảy chỗ mỗi lần đổi tab. ::deep vì .mud-tabs-panels do MudTabs render, CSS scoped không chạm thẳng
   tới được (cùng cách DeviceProfileForm). */
.ro-tabs[b-kqubhcqda5]  .mud-tabs-panels {
    height: 52vh;
    overflow-y: auto;
    padding-top: 8px;
    padding-right: 8px;
}

/* Màn hẹp: trả về tự nhiên — hộp cuộn lồng trong hộp đang cuộn rất khó dùng trên điện thoại. */
@media (max-width: 600px) {
    .ro-tabs[b-kqubhcqda5]  .mud-tabs-panels {
        height: auto;
        overflow-y: visible;
    }
}

/* Bảng cột / thẻ tổng có tới 7 cột: lề ô mặc định của MudSimpleTable Dense (6px 16px) đẩy bảng sang cuộn ngang. Bậc chọn lọc
   + thuộc tính scope thắng đúng một bậc rule gốc (cùng lý do DeviceProfileForm.razor.css). */
.mud-simple-table.mud-table-dense tr td[b-kqubhcqda5],
.mud-simple-table.mud-table-dense tr th[b-kqubhcqda5] {
    padding: 3px 6px;
}
/* /Components/Pages/Reports/ReportPrintChoiceButton.razor.rz.scp.css */
/* ADR-015 GĐ8-3: nút mũi tên của nút In tách đôi — MudMenu vẽ nút biểu tượng mang min-width / padding của nút thường (rộng ~100px). */
.ft-print-choice[b-d7di3f32h8] {
    display: inline-flex;
}

.ft-print-choice[b-d7di3f32h8]  .mud-menu .mud-icon-button {
    min-width: 0;
    padding-left: 2px;
    padding-right: 2px;
}

/* Mũi tên cao bằng nút In (thử tay 2026-10-13: 30,75px so với 34,75px — lệch 2px trên / dưới): MudMenu đặt align-self: center cho .mud-menu và
   vẽ mũi tên thành nút biểu tượng (đệm 3px, nút In đệm 5px) — giãn theo chiều cao nhóm nút thì hai nửa bằng nhau ở mọi Size. */
.ft-print-choice[b-d7di3f32h8]  .mud-menu {
    align-self: stretch;
}
/* /Components/Pages/Scada/ScadaColorField.razor.rz.scp.css */
/* Ô màu + mã hex trên một hàng. Ô màu là <span> của CHÍNH component (không ::deep); ô nhập là của MudBlazor.
   ⛔ Màu phải là LỚP ẢNH NỀN ĐẦU TIÊN, không phải `background-color`: trong CSS ảnh nền vẽ ĐÈ LÊN màu nền, nên
   để ca-rô ở ảnh và màu ở `background-color` thì ca-rô phủ lên mọi màu — xanh lá và trắng đều ra lấm tấm (đã
   dựng thử và thấy tận mắt 2026-09-07). Xếp `linear-gradient(var(--sw), var(--sw))` lên trước hai lớp ca-rô:
   màu đục che hết ca-rô, màu trong suốt thì ca-rô hiện xuyên qua — đúng bản chất.

   Dời từ `ScadaPropertiesPanel.razor.css` sang đây 2026-09-14 khi khuôn ô màu thành component dùng chung:
   markup nay thuộc scope của component này nên luật phải đi theo, để nguyên bên kia là rule CHẾT. */

/* `min-width: 0` để ô màu CO được khi nằm trong một hàng ngang chật (hàng rule của binding SVG) —
   thiếu nó thì flex item lấy min-width theo nội dung và tràn ra ngoài. Vô hại ở ngữ cảnh cột. */
.scada-props-color[b-3e19bi7ncr] { display: flex; align-items: center; gap: 6px; min-width: 0; }

.scada-props-color > .scada-props-swatch[b-3e19bi7ncr] {
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    border-radius: 4px;
    border: 1px solid var(--mud-palette-lines-default);
    background-image:
        linear-gradient(var(--sw, transparent), var(--sw, transparent)),
        linear-gradient(45deg, rgba(128, 128, 128, 0.35) 25%, transparent 25%, transparent 75%, rgba(128, 128, 128, 0.35) 75%),
        linear-gradient(45deg, rgba(128, 128, 128, 0.35) 25%, transparent 25%, transparent 75%, rgba(128, 128, 128, 0.35) 75%);
    background-size: auto, 8px 8px, 8px 8px;
    background-position: 0 0, 0 0, 4px 4px;
}

/* Ô nhập ăn hết phần còn lại. ⛔ Siết ở ĐÂY (`.mud-input-control` chỉ bọc ô nhập) chứ không đặt `Style`
   lên `MudColorPicker` — style đó bám vào gốc picker, nơi chứa cả thân bảng màu. */
.scada-props-color[b-3e19bi7ncr]  .mud-input-control { flex: 1 1 auto; min-width: 0; }
/* /Components/Pages/Scada/ScadaConfigSection.razor.rz.scp.css */
/* Tiêu đề vùng: cùng ngôn ngữ chữ HOA-nhỏ của .scada-card-title trên canvas, nhưng là chrome của FORM
   nên cỡ cố định (không cqh) + kẻ chân theo palette Mud để đúng ở cả light/dark. */
.scfg-title[b-gaz0fqa31a] {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    opacity: 0.6;
    padding-bottom: 3px;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

/* "Không áp dụng cho loại đối tượng này" — mờ, nghiêng, KHÔNG phải control. */
.scfg-na[b-gaz0fqa31a] {
    opacity: 0.45;
    font-size: 12px;
    font-style: italic;
    padding: 0 0 2px;
}

/* Thân vùng = lưới 2 cột TỰ XẾP: control chảy vào 2 cột khi dialog rộng, tự co 1 cột khi hẹp
   (min(100%, 340px) để không tràn ngang màn nhỏ). auto-FILL chứ không auto-fit: vùng chỉ có 1 control
   (vd Nhận dạng chỉ có Nhãn) vẫn giữ 2 track ⇒ control nằm nửa trái, không phình full-width trở lại. */
.scfg-body[b-gaz0fqa31a] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr));
    gap: 10px 14px;
    align-items: start;
}

/* Chú thích + cảnh báo là CÂU VĂN — chiếm trọn hàng, không chen nửa cột với control.
   ::deep vì các phần tử này do MudBlazor render (không mang scope attribute).
   ⛔ PHẢI là CON TRỰC TIẾP (`::deep >`). Bản cũ viết kiểu hậu duệ với lý lẽ "khớp phần tử nằm sâu hơn thì
   thuộc tính trơ, vô hại" — đúng cho tới khi thân vùng chứa một LƯỚI LỒNG: chú thích trong lưới đó lại
   thành grid item, `grid-column: 1 / -1` sống dậy và ép nó chiếm trọn hàng. Đã dính thật 2026-09-12: hai
   nhãn Chạy / Dừng của khối ảnh trạng thái nằm chồng lên nhau thay vì đứng cạnh nhau trên hai ô ảnh.
   Item của dropdown render ở popover (portal, ngoài .scfg-body) nên không bị vạ lây. */
.scfg-body[b-gaz0fqa31a]  > .mud-typography-caption {
    grid-column: 1 / -1;
    margin: 0;
}
.scfg-body[b-gaz0fqa31a]  > .mud-alert {
    grid-column: 1 / -1;
}

/* Biến thể DENSE — vùng toàn control ngắn (ô số, ô màu hex, công tắc): ô ~140px → 6 ô một hàng trên dialog
   Medium (user chốt 2026-08-24 — bản 180px/5 ô vẫn bị chê rộng). 140px vừa khít ô màu (ô vuông màu + mã hex
   7 ký tự + nút mở bảng chọn); màn hẹp lưới tự co còn ít cột hơn nên không tràn. Ô số dư chút nhưng ĐỀU
   nhau — đều quan trọng hơn khít từng ô. */
.scfg-body.scfg-dense[b-gaz0fqa31a] {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 140px), 1fr));
    gap: 8px 12px;
}

/* Biến thể STACK — khối editor phức tạp (series chart, SVG binding, upload ảnh…): xếp DỌC thuần,
   không vào lưới — các hàng editor tự lo bố cục ngang của chúng, lưới 2 cột sẽ bóp vỡ.
   ⛔ `align-items: stretch` là BẮT BUỘC, đừng gỡ: luật gốc `.scfg-body` đặt `align-items: start` cho biến
   thể LƯỚI, nơi nó nghĩa là "không giãn theo chiều DỌC". Sang flex-column thì cùng từ đó lại nghĩa là
   "không giãn theo chiều NGANG" ⇒ mọi khối editor co đúng bằng nội dung, chừa một mảng trống bên phải
   (đo được 122px trên 274px trong bảng 300px; user báo bằng ảnh 2026-09-12). */
.scfg-body.scfg-stack[b-gaz0fqa31a] {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
}
/* /Components/Pages/Scada/ScadaLibraryBrowser.razor.rz.scp.css */
/* ScadaLibraryBrowser — cột trái (bộ ký hiệu) + lưới thẻ. Dùng chung cho trang Thư viện hình (.is-fill) và hộp
   chọn (.is-picker).

   ── Chiều cao: khung LUÔN có chiều cao xác định, CUỘN BÊN TRONG từng khung ──
   Khuôn DeviceTreePanel.razor.css (user chốt 2026-09-08 cho màn giám sát): cột trái dài thì cuộn trong cột trái,
   lưới dài thì cuộn trong lưới, thanh công cụ đứng yên. Bản đầu của file này để khung cao theo nội dung ⇒ user báo
   2026-09-25: trang không bung hết, cột trái (thẻ viền) dừng lưng chừng — ĐÚNG hai triệu chứng DeviceTreePanel từng
   gặp. Hộp chọn: chiều cao do nơi gọi truyền (Height). Trang: .is-fill khít viewport. */

.lib[b-fi9lg3mv4c] {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
    min-height: 0;
}

/* --ft-fill-chrome = phần chiều cao NGOÀI khung (AppBar + padding container + tiêu đề trang + padding MudPaper
   trên/dưới) cộng khoảng thở đáy. Số dưới đây CHỈ LÀ MẶC ĐỊNH khi script không nạp được — lúc chạy ftFillHeight
   ĐO khoảng cách thật tới đỉnh khung và bù phần tràn bên dưới (wwwroot/js/fill-height.js). `dvh` chứ không `vh`:
   thanh địa chỉ di động co giãn làm `vh` sai. */
.lib.is-fill[b-fi9lg3mv4c] {
    --ft-fill-chrome: 196px;
    height: calc(100dvh - var(--ft-fill-chrome, 196px));
    min-height: 420px;   /* màn thấp: thà cuộn trang còn hơn bóp khung còn vài hàng thẻ */
}

.lib.is-picker[b-fi9lg3mv4c] {
    gap: 8px;
}

/* ── Thanh trên ── */
.lib-toolbar[b-fi9lg3mv4c] {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 16px;
}

.lib-search[b-fi9lg3mv4c] {
    flex: 1 1 240px;
    max-width: 380px;
}

.lib-count[b-fi9lg3mv4c] {
    margin-left: auto;
    font-size: 0.8125rem;
    color: var(--mud-palette-text-secondary);
    white-space: nowrap;
}

/* ── Thân: cột trái + lưới ──
   flex 1 + min-height 0: chiếm NỐT phần còn lại dưới thanh công cụ (thanh công cụ có thể xuống 2 dòng ở màn hẹp —
   flex hấp thụ, không phải cộng tay vào --ft-fill-chrome). ⚠️ min-height:0 BẮT BUỘC: mặc định min-height của flex
   item là auto ⇒ con tràn ra ngoài thay vì cuộn. stretch: cột trái cao bằng lưới, viền kéo tới đáy. */
.lib-body[b-fi9lg3mv4c] {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    gap: 16px;
    align-items: stretch;
}

.lib-rail[b-fi9lg3mv4c] {
    flex: 0 0 220px;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow-y: auto;
    padding: 6px 0;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    background: var(--mud-palette-surface);
}

.lib.is-picker .lib-rail[b-fi9lg3mv4c] {
    flex-basis: 184px;
}

.lib-rail-head[b-fi9lg3mv4c] {
    padding: 10px 14px 4px;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

.lib-rail-item[b-fi9lg3mv4c] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    padding: 7px 14px 7px 11px;
    border: 0;
    border-left: 3px solid transparent;
    background: none;
    font: inherit;
    font-size: 0.875rem;
    text-align: left;
    color: var(--mud-palette-text-primary);
    cursor: pointer;
}

.lib-rail-item:hover[b-fi9lg3mv4c] {
    background: var(--mud-palette-action-default-hover);
}

.lib-rail-item:focus-visible[b-fi9lg3mv4c] {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -2px;
}

.lib-rail-item.is-active[b-fi9lg3mv4c] {
    border-left-color: var(--mud-palette-primary);
    background: var(--mud-palette-primary-hover);
    color: var(--mud-palette-primary);
    font-weight: 500;
}

/* Đang tìm mà nhóm không khớp gì: làm mờ, vẫn bấm được (số 0 đã nói rõ). */
.lib-rail-item.is-empty[b-fi9lg3mv4c] {
    opacity: 0.5;
}

.lib-rail-label[b-fi9lg3mv4c] {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lib-n[b-fi9lg3mv4c] {
    font-size: 0.75rem;
    font-weight: 400;
    color: var(--mud-palette-text-secondary);
}

.lib-rail-item.is-active .lib-n[b-fi9lg3mv4c] {
    color: var(--mud-palette-primary);
}

.lib-rail-sep[b-fi9lg3mv4c] {
    margin: 6px 0;
    border-top: 1px solid var(--mud-palette-lines-default);
}

/* Lưới cuộn riêng. Padding chừa chỗ cho viền focus/viền chọn của thẻ sát mép — overflow cắt mọi thứ tràn ra ngoài. */
.lib-main[b-fi9lg3mv4c] {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
    padding: 2px 6px 2px 2px;
}

.lib-section + .lib-section[b-fi9lg3mv4c] {
    margin-top: 20px;
}

.lib-section-head[b-fi9lg3mv4c] {
    margin: 0 0 8px;
    font-size: 0.9375rem;
    font-weight: 500;
}

.lib-empty[b-fi9lg3mv4c] {
    padding: 32px 8px;
    text-align: center;
    color: var(--mud-palette-text-secondary);
}

/* ── Lưới thẻ ──
   dense: thẻ họ đang mở chèn một khung ngang trọn hàng NGAY SAU nó; dense cho các thẻ sau lấp nốt chỗ trống của
   hàng đang dở, nên khung mở luôn nằm dưới đúng hàng chứa thẻ họ thay vì cắt hàng làm đôi. */
.lib-grid[b-fi9lg3mv4c] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    grid-auto-flow: dense;
    gap: 12px;
}

.lib.is-picker .lib-grid[b-fi9lg3mv4c] {
    grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
    gap: 8px;
}

/* Thẻ là ô lưới ⇒ tự cao bằng hàng; flex column + .lib-hit flex:1 để NÚT BẤM phủ trọn chiều cao thẻ — không thì
   thẻ nào thấp nội dung hơn hàng sẽ có một dải đáy bấm không ăn. */
.lib-card[b-fi9lg3mv4c] {
    position: relative;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    background: var(--mud-palette-surface);
}

.lib-card:hover[b-fi9lg3mv4c] {
    border-color: var(--mud-palette-primary);
}

.lib-card.is-open[b-fi9lg3mv4c],
.lib-card.is-selected[b-fi9lg3mv4c] {
    border-color: var(--mud-palette-primary);
    box-shadow: 0 0 0 1px var(--mud-palette-primary);
}

.lib-hit[b-fi9lg3mv4c] {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    width: 100%;
    padding: 6px 6px 8px;
    border: 0;
    border-radius: 8px;
    background: none;
    font: inherit;
    color: inherit;
    text-align: center;
    cursor: pointer;
}

.lib-hit:focus-visible[b-fi9lg3mv4c] {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 1px;
}

/* Nền ô ảnh cố ý SÁNG ở cả hai theme: SVG trong <img> không thừa hưởng màu chữ, nét `currentColor` của ký hiệu
   P&ID luôn ra ĐEN — đặt trên nền tối là biến mất. */
.lib-thumb[b-fi9lg3mv4c] {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100px;
    overflow: hidden;
    border-radius: 6px;
    background: #f4f6f8;
}

.lib.is-picker .lib-thumb[b-fi9lg3mv4c] {
    height: 76px;
}

.lib-thumb img[b-fi9lg3mv4c] {
    max-width: 92%;
    max-height: 90%;
    object-fit: contain;
}

.lib-name[b-fi9lg3mv4c] {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: 2.6em;
    margin-top: 6px;
    font-size: 0.8125rem;
    line-height: 1.3;
    overflow-wrap: anywhere;
}

.lib-badge[b-fi9lg3mv4c] {
    position: absolute;
    top: 9px;
    right: 9px;
    padding: 1px 7px;
    border-radius: 10px;
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
    font-size: 0.6875rem;
    line-height: 1.5;
    pointer-events: none;
}

/* Menu ⋮ chỉ hiện khi rê chuột / đang focus — đỡ rối cho lưới. Máy cảm ứng không có hover nên hiện luôn. */
.lib-menu[b-fi9lg3mv4c] {
    position: absolute;
    top: 2px;
    right: 2px;
    opacity: 0;
    transition: opacity 0.12s;
}

.lib-card:hover .lib-menu[b-fi9lg3mv4c],
.lib-card:focus-within .lib-menu[b-fi9lg3mv4c] {
    opacity: 1;
}

@media (hover: none) {
    .lib-menu[b-fi9lg3mv4c] {
        opacity: 1;
    }
}

/* ── Dải kiểu của thẻ họ đang mở ── */
.lib-expand[b-fi9lg3mv4c] {
    grid-column: 1 / -1;
    padding: 10px 12px 12px;
    border: 1px solid var(--mud-palette-primary);
    border-radius: 8px;
    background: var(--mud-palette-background);
}

.lib-expand-head[b-fi9lg3mv4c] {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
    font-size: 0.875rem;
    font-weight: 500;
}

.lib-expand-close[b-fi9lg3mv4c] {
    margin-left: auto;
}

.lib-expand-grid[b-fi9lg3mv4c] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(124px, 1fr));
    gap: 8px;
}

/* ── Màn hẹp: cột trái thành dải chip cuộn ngang trên đầu lưới ── */
@media (max-width: 599.98px) {
    .lib-body[b-fi9lg3mv4c] {
        flex-direction: column;
        align-items: stretch;
    }

    .lib-rail[b-fi9lg3mv4c],
    .lib.is-picker .lib-rail[b-fi9lg3mv4c] {
        flex: none;
        flex-direction: row;
        gap: 6px;
        padding: 4px;
        overflow-x: auto;
        overflow-y: hidden;
    }

    .lib-rail-head[b-fi9lg3mv4c],
    .lib-rail-sep[b-fi9lg3mv4c] {
        display: none;
    }

    .lib-rail-item[b-fi9lg3mv4c] {
        width: auto;
        padding: 4px 10px;
        border-left: 0;
        border-radius: 16px;
        white-space: nowrap;
    }

    .lib-grid[b-fi9lg3mv4c],
    .lib.is-picker .lib-grid[b-fi9lg3mv4c] {
        grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
        gap: 8px;
    }
}
/* /Components/Pages/Scada/ScadaObjectPalette.razor.rz.scp.css */
/* ScadaObjectPalette — bảng đối tượng bên trái canvas (Đợt 19). Mirror khuôn ScadaPropertiesPanel.razor.css:
   bề rộng HẰNG SỐ, chỉ đổi khi thu gọn; thân cuộn dọc; màu qua CSS var để theo theme. */

.scada-pal[b-yeqdv7lxg5] {
    flex: 0 0 240px;
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 4px;
    overflow: hidden;
}
.scada-pal.is-collapsed[b-yeqdv7lxg5] { flex-basis: 40px; }
/* Nhúng trong bảng thuộc tính (GĐ2a): bỏ bề rộng cột + viền/nền riêng — host đã có khung của nó, giữ lại là
   hai lớp viền lồng nhau. Chiếm trọn chỗ host cho và tự cuộn bên trong. */
.scada-pal.is-embedded[b-yeqdv7lxg5] {
    flex: 1 1 auto;
    min-height: 0;
    width: auto;
    border: none;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
}

.scada-pal-head[b-yeqdv7lxg5] {
    display: flex;
    align-items: center;
    gap: 4px;
    min-height: 36px;
    padding: 2px 6px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    flex: 0 0 auto;
}
/* Thu gọn: căn giữa chevron trong dải 40px + bỏ gạch ngang (không có gì bên dưới để ngăn).
   Cặp rule này mirror `.scada-props.is-collapsed .scada-props-head` — thiếu nó thì chevron lệch trái, bị cắt,
   và còn thừa một vệt kẻ trơ. */
.scada-pal.is-collapsed .scada-pal-head[b-yeqdv7lxg5] {
    justify-content: center;
    padding: 2px 0;
    border-bottom: 0;
}
/* Tên bảng — bốn thuộc tính chữ phải KHỚP `.scada-props-title` của bảng phải (cỡ · giãn chữ · in hoa · mờ):
   hai bảng kẹp hai bên canvas nên lệch kiểu chữ là thấy ngay. In hoa bằng `text-transform`, KHÔNG gõ hoa vào
   resx — cách này đúng cho cả 4 culture (Khmer không có chữ hoa nên tự bỏ qua) và giữ giá trị resx dạng thường
   để chỗ khác dùng lại được. Bốn dòng đầu là layout riêng của bảng trái: tên nằm SAU nút gập nên phải co giãn
   và cắt bằng ba chấm. */
.scada-pal-title[b-yeqdv7lxg5] {
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    opacity: 0.7;
}

/* Thanh chọn tab — MudToggleGroup do MudBlazor render ⇒ cần ::deep để kéo đủ bề rộng. */
.scada-pal-tabs[b-yeqdv7lxg5] { padding: 6px 8px 0; }
.scada-pal-tabs[b-yeqdv7lxg5]  .mud-toggle-group { width: 100%; }
.scada-pal-tabs[b-yeqdv7lxg5]  .mud-toggle-item { flex: 1 1 0; }

.scada-pal-body[b-yeqdv7lxg5] {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 6px 8px 8px;
    display: flex;
    flex-direction: column;
}
.scada-pal-search[b-yeqdv7lxg5] { margin-bottom: 4px; }

/* Header nhóm = nút thật (bàn phím/tab tới được), không phải div bắt click. */
.scada-pal-group-head[b-yeqdv7lxg5] {
    display: flex;
    align-items: center;
    gap: 4px;
    width: 100%;
    margin-top: 4px;
    padding: 5px 4px;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: inherit;
    font: inherit;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    opacity: 0.75;
    cursor: pointer;
}
.scada-pal-group-head:hover:not(:disabled)[b-yeqdv7lxg5] { background: var(--mud-palette-action-default-hover); opacity: 1; }
/* Khoá khi đang tìm kiếm (mọi nhóm bị ép mở) — con trỏ phải nói ra điều đó. */
.scada-pal-group-head:disabled[b-yeqdv7lxg5] { cursor: default; opacity: 0.5; }
.scada-pal-group-title[b-yeqdv7lxg5] { flex: 1 1 auto; min-width: 0; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.scada-pal-count[b-yeqdv7lxg5] {
    min-width: 18px;
    padding: 1px 5px;
    border-radius: 9px;
    font-size: 10px;
    font-weight: 600;
    text-align: center;
    background: var(--mud-palette-lines-default);
}

/* Mục = nút thật, đủ cao để bấm (≥32px), viền chỉ hiện khi rê chuột. Bước 2 (kéo-thả) sẽ đặt draggable lên chính nút này. */
.scada-pal-item[b-yeqdv7lxg5] {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    min-height: 32px;
    margin: 1px 0;
    padding: 5px 8px;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.scada-pal-item:hover[b-yeqdv7lxg5] { background: var(--mud-palette-action-default-hover); border-color: var(--mud-palette-lines-default); }
.scada-pal-item[draggable="true"][b-yeqdv7lxg5] { cursor: grab; }   /* mục kéo được — con trỏ bàn tay là tín hiệu duy nhất trước khi đọc dòng gợi ý */
/* ⚠️ Phải đứng SAU rule [draggable] và mang thêm một điều kiện: hai selector cùng specificity thì cái sau thắng,
   nên `:active { cursor: grabbing }` đặt trước sẽ là mã chết (mọi mục đều draggable). */
.scada-pal-item[draggable="true"]:active[b-yeqdv7lxg5] { background: var(--mud-palette-primary-hover); cursor: grabbing; }

/* Hình thu nhỏ của mục HỌ THIẾT BỊ — chiếm đúng ô mà MudIcon Size.Small chiếm ở các mục khác, để hai
   loại mục thẳng hàng chữ. `object-fit: contain` giữ tỉ lệ: aptomat tép cao 160/rộng 35 mà ép vuông là
   méo hẳn. `pointer-events: none` để cú kéo bắt đầu từ NÚT (nơi mang data-scada-svg) — kéo trúng ảnh
   thì trình duyệt coi là kéo ẢNH và gửi payload của nó, cú thả rơi vào nhánh "kéo file" bị chặn. */
.scada-pal-thumb[b-yeqdv7lxg5] {
    width: 20px;
    height: 20px;
    flex: 0 0 auto;
    object-fit: contain;
    pointer-events: none;
}

/* Glyph đơn sắc (ScadaFamilyIconCatalog) — cùng ô 20px với .scada-pal-thumb để hai loại mục thẳng
   hàng chữ; `color: inherit` để SVG bên trong (stroke="currentColor") ăn đúng màu chữ của nút, tự đổi
   theo theme sáng/tối như MudIcon. `display:flex` để SVG con (width/height 100%) lấp trọn ô vuông. */
.scada-pal-glyph[b-yeqdv7lxg5] {
    width: 20px;
    height: 20px;
    flex: 0 0 auto;
    display: flex;
    color: inherit;
    pointer-events: none;
}
.scada-pal-glyph > svg[b-yeqdv7lxg5] { width: 100%; height: 100%; }
/* ⚠️ `.scada-pal-item > span` (dưới, "Mục = nút thật") khớp CẢ span này — nó có từ hồi mỗi mục chỉ có
   MỘT span (chữ nhãn), và độ đặc hiệu cao hơn (.scada-pal-item + span > .scada-pal-glyph một mình) nên
   THẮNG, ép khung icon giãn ngang bằng khung chữ thay vì giữ 20px cố định: icon tự co lại giữa khung to
   nên "trông vẫn ổn", nhưng khung chữ bị đẩy bắt đầu từ giữa hàng — chữ trông như canh giữa. Giành lại
   bằng độ đặc hiệu cao hơn, cùng khuôn đã vá cho `.scada-pal-layer-main > .scada-pal-count` bên dưới. */
.scada-pal-item > .scada-pal-glyph[b-yeqdv7lxg5] { flex: 0 0 auto; }

/* Tiêu điểm bàn phím: reset của MudBlazor tắt outline mặc định của <button>, mà palette có 29 nút NATIVE
   (5 tiêu đề nhóm + 24 mục) — không có rule này thì người dùng phím Tab đi trong palette mà không thấy mình ở đâu. */
.scada-pal-item:focus-visible[b-yeqdv7lxg5],
.scada-pal-group-head:focus-visible[b-yeqdv7lxg5] {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -2px;
}
.scada-pal-item > span[b-yeqdv7lxg5] { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.scada-pal-hint[b-yeqdv7lxg5] {
    padding: 8px 4px;
    font-size: 12px;
    font-style: italic;
    opacity: 0.6;
}
/* `.scada-pal-hint-drag` đã gỡ cùng 3 dòng gợi ý (user 2026-09-07: thao tác tự hiểu, không cần chú giải chiếm
   chỗ ở đáy mọi tab). Rule đó nay là mã chết nên xoá luôn — giữ lại là mời người sau tưởng còn dùng. */

/* Tab Lớp: hàng = [tay nắm kéo] [nút chính: icon · tên · z] [khoá] [ẩn]. Hàng nằm trong .mud-drop-item của
   MudDropContainer (kéo để sắp thứ tự lớp) — wrapper đó do MudBlazor render nên mọi rule chạm nó cần ::deep.
   Nút chính con trỏ tay chỉ (bấm chọn), tay nắm con trỏ grab. Hàng đang chọn tô cùng tông với object đang sáng
   trên canvas; hàng ẩn mờ đi. */
/* Thanh "N đã chọn ✕" đầu tab Lớp — chỉ hiện khi đang chọn; dính trên cùng để cuộn danh sách vẫn với tới nút thoát. */
/* Ô đếm vùng chọn — LUÔN chiếm chỗ, kể cả khi chưa chọn gì, để danh sách bên dưới không nhảy (user 2026-09-07).
   Vì luôn hiện nên nền phải TRUNG TÍNH lúc rỗng, chỉ tô tông chính khi thật sự có vùng chọn — nền sáng thường
   trực cho số 0 là báo hiệu sai. Dính trên cùng để cuộn sâu vẫn với tới nút thoát. */
.scada-pal-selbar[b-yeqdv7lxg5] {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 4px;
    margin-bottom: 4px;
    padding: 2px 2px 2px 6px;
    border-radius: 4px;
    background: var(--mud-palette-action-default-hover);
    font-size: 11px;
    font-weight: 600;
}
.scada-pal-selbar.is-active[b-yeqdv7lxg5] { background: var(--mud-palette-primary-hover); }
/* `tabular-nums` + bề rộng tối thiểu: 1 → 2 chữ số không làm nút ✕ dịch ngang. */
.scada-pal-selcount[b-yeqdv7lxg5] {
    flex: 1 1 auto;
    min-width: 0;
    font-variant-numeric: tabular-nums;
    opacity: 0.55;
}
.scada-pal-selbar.is-active > .scada-pal-selcount[b-yeqdv7lxg5] { opacity: 1; }
.scada-pal-selbar > .scada-pal-layer-tool[b-yeqdv7lxg5] { opacity: 0.8; }
/* Nút tắt vẫn khớp `:hover` theo CSS, mà nền hover của nó (`action-default-hover`) TRÙNG nền thanh lúc rỗng
   ⇒ phải chặn, kẻo rê chuột lên nút chết vẫn thấy đổi nền như thể bấm được. */
.scada-pal-selbar > .scada-pal-layer-tool:disabled[b-yeqdv7lxg5] { opacity: 0.25; cursor: default; }
.scada-pal-selbar > .scada-pal-layer-tool:disabled:hover[b-yeqdv7lxg5] { background: none; opacity: 0.25; }

.scada-pal-layer[b-yeqdv7lxg5] { display: flex; align-items: stretch; gap: 2px; margin: 1px 0; }
.scada-pal-grip[b-yeqdv7lxg5] { flex: 0 0 auto; display: flex; align-items: center; opacity: 0.3; cursor: grab; }
.scada-pal-layer:hover .scada-pal-grip[b-yeqdv7lxg5] { opacity: 0.8; }
.scada-pal-grip:active[b-yeqdv7lxg5] { cursor: grabbing; }

/* ⚠️ padding-top BẮT BUỘC: `AllowReorder` làm MudDropZone render THƯỜNG TRỰC một phần tử
   `.mud-drop-item-preview-start` — MudBlazor.min.css cho nó `height:20px; position:absolute; top:0; z-index:1`
   (đã tra trong bản 9.4.0 đang ghim). Zone là `position:relative` nên `top:0` = mép PADDING; không chừa padding
   thì dải 20px đó phủ nửa trên của HÀNG ĐẦU (~34px) và nuốt cú bấm ở đúng chỗ tự nhiên nhất của hàng đó.
   Chừa 20px cho nó nằm trong padding: vẫn là đích thả "chèn lên đầu", mà không đè hàng nào. */
[b-yeqdv7lxg5] .scada-pal-dropzone { display: flex; flex-direction: column; min-height: 8px; padding-top: 20px; }
/* Class này do `ItemDraggingClass` của MudDropContainer cấp — MudBlazor KHÔNG tự phát nó (đã tra: 0 lần xuất hiện
   trong MudBlazor.min.css/js), không truyền tham số đó thì rule dưới là mã chết. */
[b-yeqdv7lxg5] .mud-drop-item.mud-drop-item-dragging .scada-pal-layer { opacity: 0.35; }   /* hàng đang bay: mờ tại chỗ cũ */
.scada-pal-layer-main[b-yeqdv7lxg5] { flex: 1 1 auto; min-width: 0; margin: 0; cursor: pointer; }
.scada-pal-layer-main > .scada-pal-layer-name[b-yeqdv7lxg5] { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Cột LOẠI đã bỏ khỏi hàng (240px không đủ cho tên + loại + z + 2 nút công cụ) — loại nay đọc qua icon + tooltip. */
.scada-pal-layer-main > .scada-pal-count[b-yeqdv7lxg5] { flex: 0 0 auto; }
.scada-pal-layer.is-selected > .scada-pal-layer-main[b-yeqdv7lxg5] { background: var(--mud-palette-primary-hover); border-color: var(--mud-palette-primary); }
.scada-pal-layer.is-hidden > .scada-pal-layer-main[b-yeqdv7lxg5] { opacity: 0.45; }
/* Nút công cụ ẩn/khoá: mờ khi tắt để không tranh mắt với tên; sáng + màu primary khi đang bật. */
.scada-pal-layer-tool[b-yeqdv7lxg5] {
    flex: 0 0 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    color: inherit;
    opacity: 0.4;
    cursor: pointer;
}
.scada-pal-layer-tool:hover[b-yeqdv7lxg5] { opacity: 1; background: var(--mud-palette-action-default-hover); }
.scada-pal-layer-tool.is-on[b-yeqdv7lxg5] { opacity: 1; color: var(--mud-palette-primary); }
.scada-pal-layer-tool:focus-visible[b-yeqdv7lxg5] { outline: 2px solid var(--mud-palette-primary); outline-offset: -2px; opacity: 1; }

.scada-pal-foot[b-yeqdv7lxg5] {
    padding: 6px 8px;
    border-top: 1px solid var(--mud-palette-lines-default);
}
/* /Components/Pages/Scada/ScadaPropertiesPanel.razor.rz.scp.css */
/* Bảng thuộc tính bên phải khi Sửa (Đợt 18, 2026-09-06). Bề rộng CỐ ĐỊNH — chỉ đổi khi thu gọn, KHÔNG BAO GIỜ
   đổi theo vùng chọn: đó chính là điều thanh công cụ ngang không làm được (xem ScadaPropertiesPanel.razor).
   Là flex item của .scada-work (hàng: canvas trái + panel phải, khai trong <style> của ScadaDesigner.razor). */
.scada-props[b-owmmlx3cgb] {
    flex: 0 0 300px;
    min-height: 0;
    display: flex;
    flex-direction: column;
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 4px;
    box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12);
    overflow: hidden;
}
/* Thu gọn = dải mỏng chỉ còn nút mở lại; đủ hẹp cho màn 1366 mà không mất đường quay lại. */
.scada-props.is-collapsed[b-owmmlx3cgb] {
    flex-basis: 40px;
}

.scada-props-head[b-owmmlx3cgb] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 4px 4px 4px 12px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    flex: 0 0 auto;
}
.scada-props.is-collapsed .scada-props-head[b-owmmlx3cgb] {
    justify-content: center;
    padding: 4px 0;
    border-bottom: 0;
}
/* Cùng ngôn ngữ chữ HOA-nhỏ của .scfg-title trong hộp thoại cấu hình — hai bề mặt cùng một kiểu tiêu đề. */
.scada-props-title[b-owmmlx3cgb] {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    opacity: 0.7;
}

/* Thân cuộn DỌC — nội dung dài bao nhiêu cũng không đẩy khung. */
/* Thanh tab (GĐ1b) — mirror `.scada-pal-tabs` của bảng trái; `.mud-toggle-group` do MudBlazor render nên cần
   ::deep để kéo đủ bề rộng. Ba tab chia đều trong 300px. */
/* ⛔ Ô chọn màu (GĐ1c): siết bề rộng ở `.mud-input-control`, TUYỆT ĐỐI không đặt Style/width lên chính
   `MudColorPicker` — style đó bám vào GỐC của picker, mà gốc chứa CẢ bảng màu bung ra ⇒ bảng chọn bị ép
   thành dải dọc vài chục pixel, không bấm được (bẫy đã trả giá ở tab Dashboard của DeviceProfileForm).
   Cần ::deep vì phần tử do MudBlazor render. */
.scada-props-body[b-owmmlx3cgb]  .mud-picker-color .mud-input-control { width: 100%; }

/* ⛔ Khối CSS của Ô MÀU (`.scada-props-color` + `.scada-props-swatch`) ĐÃ DỜI sang
   `ScadaColorField.razor.css` ngày 2026-09-14 — markup nay thuộc component đó nên luật phải đi theo;
   để lại bên này là rule CHẾT (scoped CSS không với sang scope của component con). */
/* NGOẠI LỆ có chủ đích của luật "ô có basis 140px ⇒ hai ô không chung hàng ở 300px" (Đợt 20, xem cuối file):
   cặp Unit + Format ngắn và đi liền nhau về nghĩa, chia đôi hàng thì đọc được mà không tốn thêm hàng.
   Phải `!important` vì luật chung kia cũng `!important` — xem ghi chú tại đó. */
.scada-props-body[b-owmmlx3cgb]  .scada-unit-format > .mud-input-control { flex: 1 1 0 !important; }
/* Cùng ngoại lệ đó cho cặp "Line width" + "Arrow size" của FreeLine (2026-09-14): hai ô SỐ ngắn, đi liền
   nhau về nghĩa (mũi tên lớn trên nét mảnh là lệch tỉ lệ, nên người dùng chỉnh chúng như một cặp), chia đôi
   vẫn đọc được nhãn — đủ ba điều kiện ở Designer-Panel-Standard §5.
   ⚠️ Cũng phải `!important` vì luật basis 140px ở khối 1 mang `!important`: thiếu nó thì hai ô tách ra hai
   hàng, tốn thêm một hàng cho thứ vốn đọc như một cặp. Inline `flex:1 1 0` trong thẻ là cho HỘP THOẠI
   (markup dùng chung) — ở bảng nó thua luật chung, nên rule này mới là cái quyết định. */
.scada-props-body[b-owmmlx3cgb]  .scada-line-sizes > .mud-input-control { flex: 1 1 0 !important; }

/* Cùng ngoại lệ, cho các cặp ô số của đồng hồ kim (2026-09-15): Min↔Max và bốn mốc cảnh báo đều là số
   NGẮN và đi liền nhau về nghĩa — người dùng đặt Min thì nghĩ ngay tới Max, đặt Hi thì nghĩ ngay tới
   HiHi. Xếp mỗi ô một hàng là tốn sáu hàng cho thứ đọc như ba cặp.
   ⚠️ `!important` vì cùng lý do với luật ngay trên: luật basis 140px ở khối 1 mang `!important`.
   ⚠️ Khe giữa hai ô do `gap` của chính hàng lo — ĐỪNG đặt `max-width: 50%` lên một bên để "chừa chỗ":
   Designer-Panel-Standard §5 đã chỉ đích danh đó là cách làm hỏng lưới hai nửa. */
.scada-props-body[b-owmmlx3cgb]  .scada-gauge-pair { gap: 8px; }
.scada-props-body[b-owmmlx3cgb]  .scada-gauge-pair > .mud-input-control { flex: 1 1 0 !important; min-width: 0; }

/* Tên object trên thanh: co giãn phần còn lại và cắt bằng ba chấm — 300px không đủ cho tên dài, mà đẩy nút
   thu gọn ra khỏi thanh thì mất lối đóng bảng. Nhãn "THUỘC TÍNH" giữ nguyên cỡ, không co. */
.scada-props-head > .scada-props-title[b-owmmlx3cgb] { flex: 0 0 auto; }
.scada-props-title-obj[b-owmmlx3cgb] {
    flex: 1 1 auto;
    min-width: 0;
    margin-left: 6px;
    font-size: 12px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── Tab DỮ LIỆU (GĐ2b): ScadaObjectDataEditor sinh ra cho hộp thoại rộng 960px, nhét vào 300px phải uốn lại
   BẰNG CSS TỪ NGOÀI (::deep) — không sửa markup component, vì cùng markup vẫn phải đúng trong hộp thoại. ── */

/* 1. Hàng ngang trong thẻ editor (MudStack Row = .d-flex.flex-row) được XUỐNG DÒNG. Ô nhập/ô chọn có basis 140px
      ⇒ hai ô không bao giờ chung một hàng ở ~276px (vd "Element id" + "Property" của một binding SVG tách ra
      hai dòng), còn ô + nút nhỏ (~36px) thì vẫn chung ⇒ nút xoá đứng cạnh ô cuối chứ không rơi xuống một
      dòng trơ trọi.
      ⚠️ !important là CỐ Ý: markup gắn inline `Style="flex:1;"` lên ô (basis 0 ⇒ mọi ô ép nhau co lại trên
      một dòng, không bao giờ xuống dòng); inline thắng mọi selector, chỉ !important đè được.
      ⚠️ CẬP NHẬT 2026-09-13 (chặng 5): khối này KHÔNG còn chạm tới ô chọn nguồn dữ liệu — bốn mối nối đã về
      `ScadaSourcePicker`, và component đó không kẹp bề rộng, cũng không nằm trong hàng ngang nào (lưới chặn:
      `FT.ProductionAPI.Tests/ScadaSourcePickerDriftTests.cs`). Thứ khối này còn gác là các hàng CÒN LẠI viết
      tay trong hai thẻ `.mud-paper`: hàng màu + nhãn của một lát biểu đồ, và của một binding SVG thì hàng
      Element id + Property, hàng Pivot/Scale/Offset, hàng Min/Max (level), cặp Format + Unit, hàng rule
      Min/Max/Match/Result. ⛔ Đừng xoá khối này vì "đã gom picker rồi" — nó chưa bao giờ chỉ dành cho picker. */
.scada-props-body[b-owmmlx3cgb]  .mud-paper .d-flex.flex-row { flex-wrap: wrap; }
.scada-props-body[b-owmmlx3cgb]  .mud-paper .d-flex.flex-row > .mud-select,
.scada-props-body[b-owmmlx3cgb]  .mud-paper .d-flex.flex-row > .mud-input-control {
    flex: 1 1 140px !important;
    min-width: 0;
    max-width: 100%;
}
/* 1a. Kẹp bề rộng cứng của Ô CHỌN nằm ở LỚP TRONG, không phải con trực tiếp của hàng — giải mã bằng ilspycmd
      2026-09-13 (MudSelect 9.4.0): `div.mud-select` bên ngoài KHÔNG mang `Style`; `Style` được đẩy xuống
      `MudInputControl` bên trong ⇒ `div.mud-input-control.mud-select style="width:150px"`. Luật ở khối 1
      nhắm `> .mud-select` là nhắm VỎ; vỏ giãn 258px mà hộp có viền bên trong vẫn 150px — đúng bẫy §7 dòng 1.
      Markup dùng chung với hộp thoại 960px nên kẹp đó ở lại (hợp lý ở hộp thoại); bảng trung hoà bằng !important.
      Ô chọn còn chịu luật này: "Property" của một binding SVG (`width:150px`) và ô "Result" của một rule
      (`flex:1; min-width:120px`).
      ⚠️ Số đo bên dưới là LỊCH SỬ, không phải hiện trạng: ba ô được đo (Source · Machine · Field của một mối
      nối binding) đã rời khỏi hàng ngang ở chặng 4 và nay do `ScadaSourcePicker` vẽ, không kẹp bề rộng nữa.
      Giữ lại vì nó là bằng chứng cho CƠ CHẾ (kẹp nằm ở lớp trong), thứ vẫn đúng với các ô còn lại.
          trước vá : Source 130,0 · Machine 258,0 · Field 120,0     (vỏ ngoài cả ba đều 258 — vô nghĩa)
          sau vá   : Source 258,0 · Machine 258,0 · Field 258,0
      ⛔ Bản đầu (1f162df) gắn !important ở VỎ và báo "đã đo" — bản thử khi ấy tự bịa DOM, đặt kẹp lên vỏ.
      Bài học nằm ở §8 bước 4: giải mã, đừng đoán theo tên class. Đo trúng phần tử người dùng NHÌN THẤY. */
.scada-props-body[b-owmmlx3cgb]  .mud-paper .d-flex.flex-row > .mud-select > .mud-input-control { max-width: 100% !important; }

/* 1b. Hàng NoWrap phải được xuống dòng TRONG BẢNG — và phải dùng !important để thắng.
      Ca thật: hàng đầu của mỗi thẻ lát/cột (tám ô màu + ô nhập nhãn + nút xoá) khai `Wrap.NoWrap`.
      Ở hộp thoại 960px thì một hàng thừa chỗ; ở bảng 300px thì tám ô màu 24px + lề 8px + nút xoá 44px
      đã ăn 244 trên 258 ⇒ ô nhập nhãn còn **14,0px**, người dùng nhìn thấy như bị lấp mất.
      ⛔ Vì sao luật `flex-wrap: wrap` ở khối 1 KHÔNG cứu được: MudBlazor khai
      `.flex-nowrap { flex-wrap: nowrap !important }` — có !important sẵn trong thư viện, nên một luật
      thường dù selector dài hơn vẫn THUA. !important nằm ở tầng cao hơn specificity.
      ⚠️ Luật này CHỈ đổi hành vi của hàng nào THẬT SỰ TRÀN; hàng vừa đủ chỗ thì wrap không làm gì.
      Hôm nay trong tab Dữ liệu chỉ có đúng một hàng khai NoWrap — chính hàng màu này. */
.scada-props-body[b-owmmlx3cgb]  .mud-paper .d-flex.flex-row.flex-nowrap { flex-wrap: wrap !important; }
/* 1c. Ô nhãn xuống dòng riêng rồi thì lề trái 8px của nó thành THỤT ĐẦU DÒNG. Lề ấy có từ khi nhãn còn
      nằm cùng dòng với ô màu (tách nó khỏi ô màu cuối); ở bảng, 1b làm nhãn luôn xuống dòng (tám ô màu
      192px + basis 140 > 258) nên lề mất lý do tồn tại. Giải mã MudTextField: `Style` KHÔNG ở gốc
      `.mud-input-control` mà ở `.mud-input` bên trong ⇒ nhắm đúng chỗ đó. Chỉ áp cho hàng NoWrap này.
      Đo: hộp nhãn left 8,0 → 0,0; rộng 206,0 → 214,0 (lấy lại đúng 8px). Hộp thoại giữ nguyên lề. */
.scada-props-body[b-owmmlx3cgb]  .mud-paper .d-flex.flex-row.flex-nowrap > .mud-input-control .mud-input { margin-left: 0 !important; }

/* 2. Chữ dài thì "…" ở cuối, không tràn ra ngoài rồi bị thân bảng (overflow-x:hidden) cắt cụt. Nhãn nổi của
      MudBlazor đã có ellipsis sẵn; ô GIÁ TRỊ của MudSelect (.mud-select-input) thì chưa. */
.scada-props-body[b-owmmlx3cgb]  .mud-select-input { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }
.scada-props-body[b-owmmlx3cgb]  .mud-select,
.scada-props-body[b-owmmlx3cgb]  .mud-input-control { min-width: 0; max-width: 100%; }
/* 3. Thẻ editor không được rộng hơn thân bảng. */
.scada-props-body[b-owmmlx3cgb]  .mud-paper { max-width: 100%; box-sizing: border-box; }

.scada-props-tabs[b-owmmlx3cgb] { padding: 6px 8px 0; flex: 0 0 auto; }
/* Tầng trên Thư viện ↔ Thuộc tính (GĐ2a). Gạch dưới tách nó khỏi hàng tab con, để mắt đọc được hai TẦNG
   chứ không tưởng là một dãy năm tab ngang hàng nhau. */
.scada-props-sections[b-owmmlx3cgb] {
    padding: 6px 8px;
    flex: 0 0 auto;
    border-bottom: 1px solid var(--mud-palette-lines-default);
}
/* Bảng đối tượng nhúng vào đây phải CHIẾM TRỌN phần còn lại và tự cuộn bên trong — min-height:0 là bắt buộc,
   thiếu nó thì flex item không chịu co và thanh cuộn của con không bao giờ xuất hiện. */
.scada-props-library[b-owmmlx3cgb] {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.scada-props-tabs[b-owmmlx3cgb]  .mud-toggle-group { width: 100%; }

.scada-props-body[b-owmmlx3cgb] {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 10px 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* ⛔ MỘT nơi quyết định khoảng cách dọc: `gap` của cột này (Đợt 22, 2026-09-08).
   Vì sao cần: MudBlazor tự gắn lề cho từng ô (`.mud-input-control-margin-dense { margin: 4px 0 }`, và ô có nhãn
   nổi là `margin-top: 8px`). Lề đó CỘNG với gap của flex chứ không gộp — margin của flex item không bao giờ
   collapse — nên khe thật giữa hai ô là 10+4+4 = 18px, ô có nhãn nổi thì 22px. Bảng thuộc tính dày đặc chuẩn là
   8-10px, tức đang gấp đôi, và tab Hiển thị có tới 9 vùng nên riêng phần trống chiếm khoảng một phần ba chiều cao.
   Cách chữa là trả quyền về MỘT chỗ: tắt lề riêng của MudBlazor, để gap là thước đo duy nhất.
   ⚠️ Chỉ tắt lề DỌC. Lề ngang không đụng — `.scada-props-row` và `.scada-props-color` xếp ngang bằng gap riêng.

   ⛔ `flex: 0 0 auto` là thủ phạm THẬT của "trống quá" (đo trong trình duyệt của user 2026-09-08): MudBlazor đặt
   `.mud-input-control { flex: 1 1 auto }`. Thân bảng là CỘT flex và thường còn khoảng dư phía dưới ⇒ ô đứng một mình
   (Nhãn · Bo góc · Cỡ chữ) là con trực tiếp của cột nên GIÃN DỌC nuốt chỗ dư: đo được 96px trong khi cùng loại ô
   nằm trong `.scada-props-row` chỉ 40px (hàng là flex NGANG, grow ăn chiều rộng, vô hại). Nhìn bên ngoài y như "khe
   quá rộng" nên hai vòng đầu đi tìm ở gap/margin — sai chỗ. Lỗi có từ trước đợt này, chỉ lộ rõ hơn khi khe co lại.
   Luật của `.scada-props-row` bên dưới đặt lại `flex: 1 1 0` cho ô TRONG hàng (cùng độ ưu tiên, đứng sau nên thắng). */
.scada-props-body[b-owmmlx3cgb]  .mud-input-control {
    margin-top: 0;
    margin-bottom: 0;
    flex: 0 0 auto;
}

/* ⛔ VÁ 2026-09-12 — luật `flex: 0 0 auto` NGAY TRÊN bóp NGANG mọi MudSelect trong bảng (user báo bằng ảnh:
   ô "Nguồn dữ liệu" chỉ rộng bằng chữ "Machine", trống một mảng bên phải).
   Vì sao chỉ MudSelect dính, còn MudTextField thì không — giải mã bằng ilspycmd (MudBlazor 9.4.0):
   MudTextField có VỎ NGOÀI chính là `.mud-input-control` ⇒ nó đứng làm grid item của `.scfg-body`, mà grid
   thì bỏ qua `flex` ⇒ vẫn tràn hết ô.
   MudSelect thì vỏ ngoài là `div.mud-select` (`display:flex`, hàng NGANG) còn `.mud-input-control` nằm BÊN
   TRONG làm flex item; MudBlazor cho nó `flex: 1 1 auto` để ăn hết bề ngang, luật trên hạ xuống `0 0 auto`
   ⇒ hộp viền co đúng bằng NỘI DUNG. Nội dung ấy là `.mud-select-filler` (span vô hình mang chữ đang chọn),
   nên mỗi select một bề rộng khác nhau — dấu hiệu nhận ra rất rõ.
   ĐO ĐƯỢC (Edge headless, bảng 300px): hộp viền 182px → 274px sau khi trả lại `flex: 1 1 auto`.
   ⚠️ Chỉ trả lại chiều NGANG: đây là flex item của hàng ngang bên trong select, không dính gì tới việc
   giãn DỌC mà luật trên đang đi chữa. */
.scada-props-body[b-owmmlx3cgb]  .mud-select .mud-input-control {
    flex: 1 1 auto;
}

.scada-props-hint[b-owmmlx3cgb] {
    font-size: 12px;
    opacity: 0.6;
    font-style: italic;
}
/* Đếm object chạm trần — đỏ, đứng thẳng, đậm (cùng ngưỡng chip cũ trên thanh công cụ). */
.scada-props-hint.is-over[b-owmmlx3cgb] {
    color: var(--mud-palette-error);
    opacity: 1;
    font-style: normal;
    font-weight: 600;
}
/* Icon "đã có nền Tối" đứng cạnh hàng nút — chỉ là cờ trạng thái, không bấm được.
   ⚠️ PHẢI có ::deep: class đặt trên <MudIcon>, mà <svg> thật do MudBlazor render nên KHÔNG mang thuộc tính
   scope của component này ⇒ thiếu ::deep là rule chết câm (đã dính 2026-09-06). */
[b-owmmlx3cgb] .scada-props-flag {
    align-self: center;
    margin-left: 4px;
    opacity: 0.7;
}
.scada-props-name-main[b-owmmlx3cgb] {
    font-weight: 600;
    font-size: 14px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.scada-props-name-sub[b-owmmlx3cgb] {
    font-size: 11px;
    opacity: 0.6;
}
/* Nhịp phân nhóm: các ô TRONG một vùng sát nhau (8px = gap), còn trước mỗi tiêu đề vùng thì hở hơn
   (8 + 6 = 14px) để mắt vẫn thấy vùng nào ra vùng nấy. Sít lại mà vẫn đọc được nhóm là nhờ chênh lệch này,
   không phải nhờ khoảng trống lớn. Tiêu đề đầu tiên không cần lề trên — đã có padding của thân. */
.scada-props-section[b-owmmlx3cgb] {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    opacity: 0.6;
    margin-top: 6px;
}
.scada-props-section:first-child[b-owmmlx3cgb] { margin-top: 0; }
/* Hàng nút icon: wrap tự nhiên trong 300px — panel không có bài toán "mọc mục = tràn hàng" như thanh ngang. */
.scada-props-actions[b-owmmlx3cgb] {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
}
/* Hai ô số cạnh nhau chia đều bề rộng. ::deep vì .mud-input-control do MudBlazor render (không mang scope attribute). */
.scada-props-row[b-owmmlx3cgb] {
    display: flex;
    gap: 8px;
    align-items: center;
}
.scada-props-row[b-owmmlx3cgb]  .mud-input-control {
    flex: 1 1 0;
    min-width: 0;
}
/* /Components/Shared/AlarmThresholdValue.razor.rz.scp.css */
.alarm-thr[b-40wiip13u1] {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    white-space: nowrap;
}
.alarm-thr-limit[b-40wiip13u1] {
    display: inline-flex;
    align-items: center;
    gap: 1px;
}
/* MudIcon vẽ svg của component KHÁC ⇒ cần ::deep (bám span của ô này). Mũi tên cùng màu chữ, cỡ theo dòng. */
.alarm-thr[b-40wiip13u1]  .alarm-thr-icon {
    font-size: 1rem;
    color: inherit;
}
/* /Components/Shared/AuthBrandHeader.razor.rz.scp.css */
.auth-brand[b-eda1ef3g9y] {
    position: fixed;
    top: 20px;
    left: 28px;
    z-index: 2;
    pointer-events: none; /* click xuyên qua, không block element bên dưới */
    user-select: none;
}

.auth-brand-tagline[b-eda1ef3g9y] {
    font-size: 0.9rem;
    letter-spacing: 0.22em;
    color: var(--mud-palette-text-primary);
    opacity: 0.75;
    text-transform: uppercase;
    font-weight: 300;
    font-family: "Roboto", "Helvetica Neue", Arial, sans-serif;
}

/* Mobile <480px: gọn lại */
@media (max-width: 480px) {
    .auth-brand[b-eda1ef3g9y] {
        top: 12px;
        left: 14px;
    }
    .auth-brand-tagline[b-eda1ef3g9y] {
        font-size: 0.7rem;
        letter-spacing: 0.14em;
    }
}
/* /Components/Shared/AuthSceneBackground.razor.rz.scp.css */
/* Scene root — fixed full viewport, dưới mọi foreground content */
.scene[b-r2fovm5ffx] {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    overflow: hidden;

    /* Light mode default: tone xám trắng neutral (CAD blueprint clean),
       không đụng brand xanh lá #2E7D32, card MudPaper trắng nổi rõ */
    --scene-bg-1:       #F5F7FA;
    --scene-bg-2:       #EAEEF2;
    --scene-bg-3:       #DDE2E8;
    --scene-grid-line:  #B0BEC5;
    --scene-grid-dot:   #90A4AE;
    --scene-line:       #455A64;
    --scene-dim:        #607D8B;
    --scene-shape-fill: #EAEEF2;
    --scene-dot:        #607D8B;

    /* Background color fallback (lúc loading SVG) */
    background-color: var(--scene-bg-2);
    transition: background-color 0.3s ease;
}

/* Dark mode: tone đen-tím match MudPaper dark #1E1E2D (cùng hue, card login tan vào BG).
   Line giữ cyan #00a8ff để nổi bật trên nền đen. */
.scene.scene-dark[b-r2fovm5ffx] {
    --scene-bg-1:       #08080E;
    --scene-bg-2:       #0E0E16;
    --scene-bg-3:       #050508;
    --scene-grid-line:  #1F2740;
    --scene-grid-dot:   #2A3550;
    --scene-line:       #3F94C2;
    --scene-dim:        #1565C0;
    --scene-shape-fill: #0E0E16;
    --scene-dot:        #2A78A8;

    background-color: var(--scene-bg-1);
}

/* SVG inline fill viewport như background-size: cover */
.scene-svg[b-r2fovm5ffx] {
    width: 100%;
    height: 100%;
    display: block;
}
/* /Components/Shared/AutoRefreshStamp.razor.rz.scp.css */
.ars[b-nkyheeq17y] {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 6px;
    min-height: 20px;
}
/* Cùng cỡ / màu chấm "Connected" của thanh công cụ dashboard (.rt-dot--ok). */
.ars-dot[b-nkyheeq17y] {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex-shrink: 0;
    background: var(--mud-palette-success);
}
/* /Components/Shared/DashboardCard.razor.rz.scp.css */
/* CSS CHUNG của thẻ dashboard (DashboardCard, 2026-10-04) — vỏ thẻ, dải đầu, lưới ô, ô cảnh báo, ô nhấn mạnh. Dời nguyên từ
   EnergyCard.razor.css (thẻ Điện là thí điểm); thẻ Máy / Generic vào sau sẽ thêm biến thể `dc-compact` + CSS của mảnh riêng ở
   file của chính thẻ đó (scoped CSS bám component ĐỊNH NGHĨA markup).
   Biến --dash-* = kiểu màu thẻ admin đặt, phát inline trên gốc (DashboardCardDisplay.StyleVars). MỌI chỗ đọc chúng đều kèm màu
   theme làm dự phòng ⇒ chưa cấu hình gì thì thẻ y như cũ. Dải đầu CỐ Ý không đọc biến nào — nó mang trạng thái thiết bị. */

/* ── Vỏ thẻ ── */
.device-card[b-rae6fplufs] {
    background: var(--dash-card-bg, var(--mud-palette-surface));
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid var(--dash-card-border, var(--mud-palette-lines-default));
    transition: transform 0.15s, box-shadow 0.15s;
    /* Cột dọc để thân lấp chiều cao lưới kéo thẻ ra (2026-10-05, xem .dc-tail). Trước đây mỗi trang dashboard tự đặt
       (Máy / Điện có, Generic không) — gom về đây cho cả ba trục cùng một hành vi. */
    display: flex;
    flex-direction: column;
}
/* ── Màu thẻ THEO TÔNG (2026-10-09, user chốt "chia thêm mode") ──
   StyleVars phát --dash-lt-* (màu sáng) + --dash-dk-* (màu riêng chế độ tối); bốn khối dưới đây ánh xạ về --dash-* mà mọi
   luật màu của thẻ đọc. Biến nguồn vắng ⇒ --dash-* thành "không hợp lệ" ⇒ var(--dash-…, màu theme) dùng màu theme — đúng
   hành vi "chưa đặt". Tối không đặt ⇒ rơi về màu sáng cùng ô (DashboardCardStyle.ForMode).
   - Trang: :root[data-ft-dark] do MainLayout đồng bộ theo tông THỰC TẾ (kể cả ghim tông của trang dashboard).
   - Khung xem trước (bảng Cấu hình): data-dash-tone ép tông độc lập với trang. Xem trước Tối trên trang Sáng = khối tối
     qua bộ chọn tone; xem trước Sáng trên trang Tối = khối thứ ba (thêm một thuộc tính ⇒ ưu tiên cao hơn khối tối).
   ⛔ Inline đặt THẲNG --dash-{tên} (vd GenericCard viền theo quy tắc màu) vẫn thắng cả bốn khối — cố ý.
   ⛔ Thêm tên ở DashboardCardDisplay.StyleVarNames thì thêm dòng ở CẢ BỐN khối (lưới DashboardCardToneWiringTests). */
.device-card[b-rae6fplufs] {
    --dash-card-bg: var(--dash-lt-card-bg);
    --dash-card-border: var(--dash-lt-card-border);
    --dash-cell-bg: var(--dash-lt-cell-bg);
    --dash-label: var(--dash-lt-label);
    --dash-value: var(--dash-lt-value);
    --dash-actual: var(--dash-lt-actual);
    --dash-target: var(--dash-lt-target);
    --dash-time-value: var(--dash-lt-time-value);
    --dash-highlight: var(--dash-lt-highlight);
    --dash-actual-bg: var(--dash-lt-actual-bg);
    --dash-target-bg: var(--dash-lt-target-bg);
    --dash-highlight-bg: var(--dash-lt-highlight-bg);
}
:root[data-ft-dark] .device-card[b-rae6fplufs],
:root [data-dash-tone="dark"] .device-card[b-rae6fplufs] {
    --dash-card-bg: var(--dash-dk-card-bg, var(--dash-lt-card-bg));
    --dash-card-border: var(--dash-dk-card-border, var(--dash-lt-card-border));
    --dash-cell-bg: var(--dash-dk-cell-bg, var(--dash-lt-cell-bg));
    --dash-label: var(--dash-dk-label, var(--dash-lt-label));
    --dash-value: var(--dash-dk-value, var(--dash-lt-value));
    --dash-actual: var(--dash-dk-actual, var(--dash-lt-actual));
    --dash-target: var(--dash-dk-target, var(--dash-lt-target));
    --dash-time-value: var(--dash-dk-time-value, var(--dash-lt-time-value));
    --dash-highlight: var(--dash-dk-highlight, var(--dash-lt-highlight));
    --dash-actual-bg: var(--dash-dk-actual-bg, var(--dash-lt-actual-bg));
    --dash-target-bg: var(--dash-dk-target-bg, var(--dash-lt-target-bg));
    --dash-highlight-bg: var(--dash-dk-highlight-bg, var(--dash-lt-highlight-bg));
}
/* Ô tối trống dùng màu THEME (DashboardCard DarkUsesTheme → data-dash-dark-theme, 2026-10-09): như khối tối nhưng KHÔNG rơi
   về --dash-lt-* — dk vắng ⇒ --dash-* không hợp lệ ⇒ var(--dash-…, màu theme). Thêm một thuộc tính ⇒ thắng khối tối; đặt
   TRƯỚC khối "xem trước Sáng" (cùng độ ưu tiên) để xem trước Sáng trên trang Tối vẫn ra màu sáng. */
:root[data-ft-dark] .device-card[data-dash-dark-theme][b-rae6fplufs],
:root [data-dash-tone="dark"] .device-card[data-dash-dark-theme][b-rae6fplufs] {
    --dash-card-bg: var(--dash-dk-card-bg);
    --dash-card-border: var(--dash-dk-card-border);
    --dash-cell-bg: var(--dash-dk-cell-bg);
    --dash-label: var(--dash-dk-label);
    --dash-value: var(--dash-dk-value);
    --dash-actual: var(--dash-dk-actual);
    --dash-target: var(--dash-dk-target);
    --dash-time-value: var(--dash-dk-time-value);
    --dash-highlight: var(--dash-dk-highlight);
    --dash-actual-bg: var(--dash-dk-actual-bg);
    --dash-target-bg: var(--dash-dk-target-bg);
    --dash-highlight-bg: var(--dash-dk-highlight-bg);
}
/* Xem trước SÁNG trên trang đang tối — xem chú thích đầu mục. */
:root[data-ft-dark] [data-dash-tone="light"] .device-card[b-rae6fplufs] {
    --dash-card-bg: var(--dash-lt-card-bg);
    --dash-card-border: var(--dash-lt-card-border);
    --dash-cell-bg: var(--dash-lt-cell-bg);
    --dash-label: var(--dash-lt-label);
    --dash-value: var(--dash-lt-value);
    --dash-actual: var(--dash-lt-actual);
    --dash-target: var(--dash-lt-target);
    --dash-time-value: var(--dash-lt-time-value);
    --dash-highlight: var(--dash-lt-highlight);
    --dash-actual-bg: var(--dash-lt-actual-bg);
    --dash-target-bg: var(--dash-lt-target-bg);
    --dash-highlight-bg: var(--dash-lt-highlight-bg);
}

.device-card:hover[b-rae6fplufs] {
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
/* Mất kết nối: giữ nguyên độ đậm — màu dải đầu đã phân biệt; số hiện "--". (Từng mờ 0.5, bỏ theo phản hồi UX.) */
.device-offline[b-rae6fplufs] { }
.device-alarm[b-rae6fplufs] {
    border-color: #F44336;
    box-shadow: 0 0 8px rgba(244, 67, 54, 0.3);
}

/* ── Dải đầu: một dòng ── */
.device-header[b-rae6fplufs] {
    display: flex;
    align-items: center;
    padding: 5px 14px;
    gap: 8px;
    min-height: 0;
}
.device-header-info[b-rae6fplufs] {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-width: 0;
    flex: 1;
}
.device-name[b-rae6fplufs] {
    font-weight: 700;
    font-size: 1rem;
    color: #fff;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex-shrink: 0;
}
.device-location[b-rae6fplufs] {
    font-size: 0.82rem;
    color: rgba(255,255,255,0.7);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
/* "Số lúc HH:mm" khi còn kết nối mà chưa có số mới — co được + "…", không đẩy tràn thẻ hẹp (tên không co). */
.device-quiet-note[b-rae6fplufs] {
    font-size: 0.76rem;
    font-style: italic;
    color: rgba(255,255,255,0.9);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
.device-status-dot[b-rae6fplufs] {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex-shrink: 0;
}
/* Tên nhường chỗ cho dòng phụ (Generic: loại thiết bị đứng cạnh tên, quan trọng ngang tên) — bật bằng Class="dc-name-shrink". */
.dc-name-shrink .device-name[b-rae6fplufs] {
    max-width: 60%;
}

/* ── Trạng thái dải đầu (màu công nghiệp, cố định — không theo theme, không theo kiểu màu) ── */
.status-running[b-rae6fplufs] { background: #2e7d32; }
.status-running .device-status-dot[b-rae6fplufs] {
    background: #fff;
    box-shadow: 0 0 5px rgba(255,255,255,0.6);
    animation: pulse-dot-b-rae6fplufs 1.5s infinite;
}
.status-idle[b-rae6fplufs] { background: #ef6c00; }
.status-idle .device-status-dot[b-rae6fplufs] { background: #fff3e0; }
.status-alarm[b-rae6fplufs] { background: #c62828; }
.status-alarm .device-status-dot[b-rae6fplufs] {
    background: #fff;
    box-shadow: 0 0 5px rgba(255,255,255,0.6);
    animation: pulse-dot-b-rae6fplufs 0.8s infinite;
}
.status-offline[b-rae6fplufs] { background: #90a4ae; }
.status-offline .device-status-dot[b-rae6fplufs] { background: #cfd8dc; }

@keyframes pulse-dot-b-rae6fplufs {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

/* ── Thân ── */
.device-body[b-rae6fplufs] {
    padding: 6px 12px 6px;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
}

/* Phần ĐUÔI (khối sau lưới ô cuối + chân thẻ) dồn xuống đáy khi thẻ cao hơn nội dung — thẻ cùng hàng trên lưới dashboard
   cao bằng thẻ cao nhất, nên KPI + hàng nút của mọi thẻ thẳng nhau (user 2026-10-05). auto = 0 khi không có chỗ thừa ⇒
   thẻ đứng riêng và phép đo chiều cao thẻ của dashboard-layout.js không đổi. */
.dc-tail[b-rae6fplufs] {
    margin-top: auto;
}

/* Lề GIỮA hai phần: trước đây khối cuối phần trên và khối đầu đuôi nằm chung một dòng chảy nên lề dọc GỘP (lấy cái lớn);
   nay mỗi phần là một mục flex nên lề CỘNG. Bỏ lề dưới của con cuối phần trên — lề trên của đuôi (khối KPI / hàng nút đều
   có) giữ đúng khoảng cũ. Con cuối là mảnh của thẻ trục (component khác vẽ) ⇒ ::deep. Lưới ô (.dc-cells) vốn không có lề. */
.dc-main:not(:last-child)[b-rae6fplufs]  > :last-child {
    margin-bottom: 0;
}

/* ── Lưới ô (số cột đặt inline theo bố cục) ── */
.dc-cells[b-rae6fplufs] {
    display: grid;
    gap: 6px;
}
.dc-cell[b-rae6fplufs] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding: 4px 10px;
    border-radius: 4px;
    background: var(--dash-cell-bg, var(--mud-palette-background-gray));
}
.dc-cell-label[b-rae6fplufs] {
    font-size: 0.8rem;
    color: var(--dash-label, var(--mud-palette-text-secondary));
    white-space: nowrap;
}
.dc-cell-value[b-rae6fplufs] {
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--dash-value, var(--mud-palette-primary));
    font-family: 'Roboto Mono', 'Consolas', monospace;
}

/* Ô nhấn mạnh (Điện: kW, kWh — hai chỉ số người vận hành nhìn đầu tiên; màu chỉnh ở "Số nổi bật" / "Nền ô nổi bật").
   Nền riêng (--dash-highlight-bg, 2026-10-09) vắng thì pha 12% từ màu số — tỉ lệ khớp DashboardCardDisplay.TintHex. */
.dc-cell--emphasis[b-rae6fplufs] {
    background: var(--dash-highlight-bg, color-mix(in srgb, var(--dash-highlight, var(--mud-palette-success)) 12%, var(--dash-card-bg, var(--mud-palette-surface))));
}
.dc-cell--emphasis .dc-cell-value[b-rae6fplufs] {
    color: var(--dash-highlight, var(--mud-palette-success));
}

/* ── Lớp giá trị do trục đặt (CardCell.ValueClass) — Máy: ngưỡng nhiệt cứng 60/80 khi ô Temp chưa có quy tắc / ngưỡng.
   !important để thắng màu giá trị mặc định của ô. */
.dc-value--warn[b-rae6fplufs] { color: #ef6c00 !important; }
.dc-value--high[b-rae6fplufs] { color: #d32f2f !important; }

/* ── Dải đầu NHÁY giữa màu trạng thái và màu cảnh báo (Máy: Andon đang mở — thấy được từ xa trong xưởng). Bật bằng
   Header.Class="dc-header-alert" + Header.Style đặt --dc-status-color / --dc-alert-color (keyframes không đọc được style
   inline nên hai màu đi qua biến). `steps(1)` = nháy DỨT KHOÁT như đèn andon thật, KHÔNG fade mượt: chuyển màu mềm nhìn
   từ xa gần như không nhận ra. 1.1s/nhịp — đủ chậm để không chói mắt cả ca, đủ nhanh để đập vào mắt.
   ⚠️ Keyframes THẮNG style inline trong cascade (tầng animation nằm trên author-normal) nên background-color inline của
   dải đầu không cần đụng tới. */
.device-header.dc-header-alert[b-rae6fplufs] {
    animation: dc-header-alert-b-rae6fplufs 1.1s steps(1, end) infinite;
}
@keyframes dc-header-alert-b-rae6fplufs {
    0%, 49%   { background-color: var(--dc-alert-color); }
    50%, 100% { background-color: var(--dc-status-color); }
}
/* Người bật "giảm chuyển động" (hoặc màn hình bị say chuyển động): KHÔNG bỏ cảnh báo — đây là tín hiệu gọi người, bỏ nháy
   mà không thay gì khác là mất luôn cảnh báo. Thay bằng nền cảnh báo TĨNH + viền dày: vẫn nổi bật, không chuyển động. */
@media (prefers-reduced-motion: reduce) {
    .device-header.dc-header-alert[b-rae6fplufs] {
        animation: none;
        background-color: var(--dc-alert-color) !important;
        box-shadow: inset 0 -3px 0 rgba(255, 255, 255, 0.85);
    }
}

/* ── Biến thể GỌN (Compact="true" — thẻ Máy): dải đầu thấp hơn, chữ nhỏ hơn, ô xếp DỌC nhãn trên số dưới, 3 cột — đúng vẻ thẻ
   máy từ trước (số đo dời nguyên từ MachineCard.razor.css). ── */
.dc-compact .device-header[b-rae6fplufs] {
    padding: 3px 12px;
    gap: 6px;
}
.dc-compact .device-header-info[b-rae6fplufs] {
    gap: 6px;
}
.dc-compact .device-name[b-rae6fplufs] {
    font-size: 0.9rem;
}
.dc-compact .device-location[b-rae6fplufs] {
    font-size: 0.78rem;
}
.dc-compact .device-quiet-note[b-rae6fplufs] {
    font-size: 0.72rem;
}
.dc-compact .device-body[b-rae6fplufs] {
    padding: 6px 10px 6px;
}
.dc-compact .dc-cells[b-rae6fplufs] {
    gap: 4px;
}
.dc-compact .dc-cell[b-rae6fplufs] {
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    padding: 3px 2px;
}
.dc-compact .dc-cell-label[b-rae6fplufs] {
    font-size: 0.62rem;
    color: var(--dash-label, var(--mud-palette-text-disabled));
    text-transform: uppercase;
    letter-spacing: 0.3px;
    white-space: normal;
}
.dc-compact .dc-cell-value[b-rae6fplufs] {
    font-size: 0.92rem;
    color: var(--dash-value, var(--mud-palette-text-primary));
    line-height: 1.2;
}

/* ── Ô vượt ngưỡng cảnh báo — nền nhạt + vạch trái + chữ giá trị theo MỨC (Warning cam / Critical đỏ), KHÔNG nháy: dải đầu
   đã đỏ + chấm nháy khi có cảnh báo, ô nháy nữa là cả bức tường màn hình nhấp nháy. Vạch trái = box-shadow inset (không
   border-left: border ăn thêm 3px, chữ xô lệch lúc ô vào/ra cảnh báo). Màu theo biến theme để tự đúng ở cả sáng lẫn tối. */
.dc-cell--alarm-warning[b-rae6fplufs] {
    background: color-mix(in srgb, var(--mud-palette-warning) 18%, var(--mud-palette-surface));
    box-shadow: inset 3px 0 0 var(--mud-palette-warning);
}
.dc-cell--alarm-warning .dc-cell-value[b-rae6fplufs] {
    color: var(--mud-palette-warning);
}
.dc-cell--alarm-critical[b-rae6fplufs] {
    background: color-mix(in srgb, var(--mud-palette-error) 18%, var(--mud-palette-surface));
    box-shadow: inset 3px 0 0 var(--mud-palette-error);
}
.dc-cell--alarm-critical .dc-cell-value[b-rae6fplufs] {
    color: var(--mud-palette-error);
}
/* /Components/Shared/DashboardCardDesigner.razor.rz.scp.css */
/* Trình thiết kế thẻ (U1, 2026-10-04): thẻ xem trước = cột lưới RỘNG CỐ ĐỊNH đúng ô lưới thật của trang
   (--dcd-stage-w do component đặt), dính khi cuộn để sửa ở tab nào cũng thấy kết quả; bốn tab nhận phần còn lại.
   ⚠️ Bản trước là flex co được (`flex: 0 1 auto`) ⇒ thẻ bị bóp còn ~300px dù trang để 480px — xem trước nói dối.
   U4 (2026-10-04, user chốt): thẻ xem trước nằm BÊN PHẢI. Đặt chỗ bằng grid-column/grid-row, KHÔNG đảo thứ tự markup:
   markup giữ thẻ trước để màn hẹp (xếp dọc, dưới) vẫn hiện thẻ TRÊN các tab — đang sửa phải thấy thẻ không cần cuộn. */
.dcd[b-9q0kr8nk8l] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--dcd-stage-w, 480px);
    gap: 16px;
    align-items: start;
}

/* 96px = app bar + lề — chỉ còn tác dụng khi trình thiết kế nằm ngoài khuôn .page-fill (xem khối LẤP bên dưới). */
.dcd-stage[b-9q0kr8nk8l] {
    grid-column: 2;
    grid-row: 1;
    position: sticky;
    top: 96px;
    min-width: 0;
}

.dcd-preview[b-9q0kr8nk8l] {
    width: 100%;
}

/* KHÔNG khung viền riêng (bỏ 2026-10-04, user: "thụt thò"): bốn tab đã nằm trong thẻ của cột phải — viền thứ hai là
   giấy trong giấy, đẩy nội dung thụt vào so với thẻ xem trước bên cạnh. */
.dcd-props[b-9q0kr8nk8l] {
    grid-column: 1;
    grid-row: 1;
    min-width: 0;
}

/* Màn chưa đủ chỗ cho thẻ + bảng thuộc tính cạnh nhau (danh sách phạm vi 364px + menu trái chiếm sẵn): xếp dọc, thẻ không
   dính (dính trên màn ngắn che mất bảng), thẻ vẫn giữ bề rộng thật nhưng không tràn. */
@media (max-width: 1499.98px) {
    .dcd[b-9q0kr8nk8l] {
        grid-template-columns: minmax(0, 1fr);
    }

    /* Trả về dòng chảy theo markup: thẻ trên, tab dưới. */
    .dcd-stage[b-9q0kr8nk8l],
    .dcd-props[b-9q0kr8nk8l] {
        grid-column: auto;
        grid-row: auto;
    }

    .dcd-stage[b-9q0kr8nk8l] {
        position: static;
    }

    .dcd-preview[b-9q0kr8nk8l] {
        width: min(100%, var(--dcd-stage-w, 480px));
    }
}

/* LẤP thân cột phải của bảng Cấu hình dashboard (2026-10-04) khi trang theo khuôn .page-fill — thân đó (.dcp-body) là cột
   flex có chiều cao (DashboardConfigPanel.razor.css). Từ 1500px (thẻ + tab cạnh nhau): trình thiết kế cao đúng thân, thẻ xem
   trước đứng yên ở đầu cột của nó (tự cuộn nếu thẻ cao hơn chỗ), tiêu đề tab con đứng yên, CHỈ nội dung tab con cuộn —
   sticky không còn việc. Dưới 1500px giữ dòng chảy: thẻ trên, tab dưới, cả thân cuộn. Không .page-fill ⇒ không áp. */
@media (min-width: 1500px) {
    .page-fill .dcd[b-9q0kr8nk8l] {
        flex: 1 1 0;
        min-height: 0;
        grid-template-rows: minmax(0, 1fr);
        align-items: stretch;
    }

    .page-fill .dcd-stage[b-9q0kr8nk8l] {
        position: static;
        align-self: start;
        max-height: 100%;
        overflow-y: auto;
    }

    .page-fill .dcd-props[b-9q0kr8nk8l] {
        display: flex;
        flex-direction: column;
        min-height: 0;
    }

    /* MudTabs do component KHÁC vẽ ⇒ ::deep. */
    .page-fill .dcd-props[b-9q0kr8nk8l]  > .mud-tabs {
        flex: 1 1 0;
        min-height: 0;
        display: flex;
        flex-direction: column;
    }

    .page-fill .dcd-props[b-9q0kr8nk8l]  > .mud-tabs > .mud-tabs-panels {
        flex: 1 1 0;
        min-height: 0;
        overflow-y: auto;
    }
}

/* Tab Bố cục: hai cột (ô | dải + nút) khi đủ chỗ, tự xếp dọc khi hẹp. */
.dcd-layout-cols[b-9q0kr8nk8l] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 8px 24px;
    align-items: start;
}

/* Ô / khối bấm chọn được — data-cell do THẺ (component khác) vẽ ⇒ phải qua ::deep. position:relative để chấm
   "có quy tắc màu" (::after, CSS phát trong component) neo vào góc ô. */
.dcd-preview[b-9q0kr8nk8l]  [data-cell] {
    cursor: pointer;
    position: relative;
}

.dcd-preview[b-9q0kr8nk8l]  [data-cell]:hover {
    outline: 1px dashed var(--mud-palette-primary);
    outline-offset: -1px;
}

.dcd-sec[b-9q0kr8nk8l] {
    margin: 12px 0 4px;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--mud-palette-text-secondary);
}

/* Ô nhập một cột. LƯỚI chứ không cột flex: MudBlazor đặt flex 1 1 auto cho ô nhập, trong cột flex dư chỗ là ô bị
   kéo cao (bẫy ghi ở Designer-Panel-Standard §6) — hàng lưới auto chỉ cao đúng nội dung. */
.dcd-fields[b-9q0kr8nk8l] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
}

/* Tiêu đề vùng kèm nút ở cuối hàng (Màu trạng thái · + Thêm quy tắc). */
.dcd-sec-row[b-9q0kr8nk8l] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

/* Một quy tắc = hai hàng, kẻ ngăn giữa các quy tắc. */
.dcd-rule-edit[b-9q0kr8nk8l] {
    display: grid;
    gap: 6px;
    padding: 8px 0;
    border-top: 1px solid var(--mud-palette-lines-default);
}

/* Hai ô chia đôi một hàng: flex 1 1 0 + min-width 0 cho ĐÚNG nửa-nửa ở mọi bề rộng (Designer-Panel-Standard §5,
   đừng kẹp max-width một bên). Phần tử do MudBlazor vẽ không mang thuộc tính scope ⇒ phải qua ::deep. */
.dcd-rule-line[b-9q0kr8nk8l] {
    display: flex;
    align-items: center;
    gap: 8px;
}

.dcd-rule-line[b-9q0kr8nk8l]  > * {
    flex: 1 1 0;
    min-width: 0;
}

/* Nút xoá giữ đúng cỡ của nó, không chia phần. */
.dcd-rule-line[b-9q0kr8nk8l]  > .dcd-rule-del {
    flex: 0 0 auto;
}

/* Vỏ ngoài MudSelect là lớp bọc xếp ngang, hộp có viền nằm BÊN TRONG — giãn vỏ chưa đủ, phải giãn cả ô nhập bên trong
   kẻo hộp co bằng chữ đang chọn (bẫy §7 dòng 1 của Designer-Panel-Standard). */
.dcd-rule-line[b-9q0kr8nk8l]  .mud-select > .mud-input-control {
    flex: 1 1 auto;
    min-width: 0;
}

/* "Khác nhau (N)" dưới ô nhập khi chọn nhiều — dòng mờ nhỏ, như bảng phải SCADA. */
.dcd-mixed[b-9q0kr8nk8l] {
    margin-top: 2px;
    font-size: 0.75rem;
    color: var(--mud-palette-text-secondary);
}

.dcd-rule-cond[b-9q0kr8nk8l] {
    font-size: 0.8125rem;
    color: var(--mud-palette-text-secondary);
    overflow-wrap: anywhere;
}

/* Danh sách thứ tự ô (kéo để sắp, B2c 2026-10-04) — hàng gọn: tay nắm · khoá · tên · sao khi đang nhấn mạnh. Hàng do
   MudDropZone (component khác) vẽ ⇒ ::deep từ .dcd-props. */
.dcd-props[b-9q0kr8nk8l]  .dcd-order {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.dcd-props[b-9q0kr8nk8l]  .dcd-order-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 6px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 4px;
    font-size: 0.8125rem;
    background: var(--mud-palette-surface);
}

.dcd-props[b-9q0kr8nk8l]  .dcd-order-key {
    font-weight: 600;
    flex: 0 0 auto;
}

.dcd-props[b-9q0kr8nk8l]  .dcd-order-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--mud-palette-text-secondary);
}

/* Dải khối đang ẩn (danh sách thứ tự dải, B2d): mờ + nhãn "(đang ẩn)" — vẫn kéo được, bố cục giữ chỗ cho lúc hiện lại. */
.dcd-props[b-9q0kr8nk8l]  .dcd-order-hidden {
    opacity: .55;
}

.dcd-props[b-9q0kr8nk8l]  .dcd-order-tag {
    flex: 0 0 auto;
    font-size: 0.75rem;
    color: var(--mud-palette-text-secondary);
}
/* /Components/Shared/DashboardColorField.razor.rz.scp.css */
/* `min-width: 0` để ô CO được trong ô lưới hẹp (DashboardSettingGrid) thay vì tràn ra ngoài. */
.dcf[b-hnteeodgjt] {
    min-width: 0;
}

/* Dòng nguồn màu dưới ô — cùng kiểu chữ phụ của bảng thuộc tính SCADA (.scada-props-hint: 12px, mờ, nghiêng).
   Thụt 30px = ô vuông 24px + khe 6px của ScadaColorField, để chữ đứng ngay dưới ô nhập chứ không dưới ô vuông. */
.dcf-hint[b-hnteeodgjt] {
    font-size: 12px;
    line-height: 16px;
    opacity: 0.6;
    font-style: italic;
    margin: 2px 0 0 30px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* /Components/Shared/DashboardConfigPanel.razor.rz.scp.css */
/* Bố cục 2 cột — mirror `.mgr-split` của GenericDeviceManagerPage (cùng khuôn trang quản lý: trái danh sách, phải thuộc tính
   của mục đang chọn). Cột trái hẹp cố định vì nó chỉ chứa tên + mã; cột phải nhận phần còn lại để bảng ô + thẻ xem trước đủ chỗ.
   Dời nguyên từ MachineDashboardConfigPanel.razor.css (`.mdc-*`) / EnergyDashboardConfigPanel.razor.css (`.edc-*`) khi gộp
   hai bảng về một (2026-10-04). */
.dcp-split[b-h1a5hyk02m] {
    display: grid;
    /* 364px = 280 cũ + 30% (user chốt 2026-08-15): cột trái mang thêm tay nắm kéo + ô tick "hiện trên dashboard" nên tên
       bị bóp còn quá hẹp. */
    grid-template-columns: minmax(0, 364px) minmax(0, 1fr);
    gap: 12px;
    align-items: start;
}

/* KHÍT VÙNG TAB (2026-10-04, user: "tham khảo iot/devices… lòi thanh cuộn, thụt thò"). Ba trang chủ theo khuôn .page-fill,
   .dcp-split mang page-fill-fit ⇒ từ lg nó cao đúng vùng tab (luật chung ở app.css). Ở đây chỉ chia chiều cao đó:
   - hai cột cao bằng nhau, cùng chạm đáy thẻ (hàng lưới minmax(0,1fr) + stretch);
   - cột trái: ô tìm + "Mặc định" đứng yên, CHỈ danh sách cuộn (.dcp-scope-list);
   - cột phải: hàng tên + Lưu đứng yên, thân (.dcp-body) nhận phần còn lại — từ 1500px trình thiết kế tự chia tiếp (thẻ xem
     trước đứng yên, chỉ nội dung tab con cuộn), dưới 1500px nó xếp dọc nên cả thân cuộn.
   Chỉ khi có .page-fill ở trên: thiếu nó thì page-fill-fit không có chiều cao, mà flex-basis 0 ở đây sẽ ép danh sách về 0.
   ⚠️ Cột là MudPaper — phần tử do component KHÁC vẽ, class scoped đặt lên nó là luật chết (bản cũ `.dcp-props { sticky }`
   chưa từng ăn) ⇒ chia cột bằng div của chính bảng (.dcp-scopes / .dcp-right) cao 100% bên trong MudPaper. */
@media (min-width: 1280px) {
    .page-fill .dcp-split[b-h1a5hyk02m] {
        min-height: 480px;   /* sàn khi phía trên mọc băng cảnh báo — thắng :where(... min-height: 0) của khuôn */
        grid-template-rows: minmax(0, 1fr);
        align-items: stretch;
    }

    .page-fill .dcp-scopes[b-h1a5hyk02m],
    .page-fill .dcp-right[b-h1a5hyk02m] {
        height: 100%;
        display: flex;
        flex-direction: column;
        min-height: 0;
    }

    /* MỌI con của hai cột cao đúng nội dung; chỉ danh sách / thân (luật dưới, mạnh hơn) nhận phần còn lại. Component
       MudBlazor tự giãn trong cột flex: MudTextField `flex: 1 1 auto` (bẫy Designer-Panel-Standard §6), MudDivider mặc định
       `.mud-divider-fullwidth { flex-grow: 1 }` — user bắt 2026-10-04: đường kẻ chia nửa chiều cao với danh sách, cột trái
       trống khúc trên. Chặn theo loại (*) chứ không theo từng component, kẻo thêm một component nữa là trống lại. */
    .page-fill .dcp-scopes[b-h1a5hyk02m]  > *,
    .page-fill .dcp-right[b-h1a5hyk02m]  > * {
        flex: none;
    }

    /* Độ ưu tiên (0,4,0) — thắng luật "mọi con" (0,3,0) ở trên bất kể thứ tự. */
    .page-fill .dcp-scopes > .dcp-scope-list[b-h1a5hyk02m],
    .page-fill .dcp-right > .dcp-body[b-h1a5hyk02m] {
        flex: 1 1 0;
        min-height: 0;
        overflow-y: auto;
    }

    /* Thân cột phải là cột flex để trình thiết kế (từ 1500px) lấp được nó. */
    .page-fill .dcp-right > .dcp-body[b-h1a5hyk02m] {
        display: flex;
        flex-direction: column;
    }
}

/* Một mục trong danh sách chọn phạm vi. Dùng list dọc click-chọn thay radio: radio render inline nên danh sách 2 mục nhảy
   chung một hàng khi hẹp (bài học HostNetworkConfigDialog). */
.dcp-item[b-h1a5hyk02m] {
    display: flex;
    align-items: center;
    padding: 6px 8px;
    border-radius: 4px;
    cursor: pointer;
    border-left: 3px solid transparent;
}

.dcp-item:hover[b-h1a5hyk02m] {
    background: var(--mud-palette-action-default-hover);
}

/* ĐANG CHỌN — viền trái đậm + nền, y ngôn ngữ hình của `.mgr-row-selected` bên Generic. ⛔ Đây là affordance của "đang
   chọn", đừng mượn nó để phân tầng cha/con (bài học GenericDeviceManagerPage 2026-08-13). */
.dcp-item-selected[b-h1a5hyk02m] {
    border-left-color: var(--mud-palette-primary);
    background: var(--mud-palette-action-default-hover);
}

@media (max-width: 1279.98px) {
    .dcp-split[b-h1a5hyk02m] { grid-template-columns: minmax(0, 1fr); }
}
/* /Components/Shared/DashboardSettingGrid.razor.rz.scp.css */
/* U2 (2026-10-04, user chốt "lưới 2 cột gọn"): đúng HAI cột trong tab Giao diện thẻ / Toàn trang — bản trước tự xuống hàng
   theo minmax(240px) nên 8 ô màu dàn 4 cột + 1 ô mồ côi ở hàng sau, mắt phải quét ngang cả màn. Mỗi ô vẫn đủ chỗ cho
   [ô vuông 24][ô hex có tên trên viền ≥ ~170][nút bỏ màu 34] — mã "#ffffff" không bị cắt (bản có cột nhãn 108px từng cắt). */
.dsg-grid[b-eilkrqox06] {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px 16px;
    align-items: start;
}

/* Bảng thuộc tính hẹp (màn nhỏ, xếp dọc): một cột. */
@media (max-width: 599.98px) {
    .dsg-grid[b-eilkrqox06] {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* Phần tử con do component KHÁC vẽ (MudSelect, DashboardColorField) ⇒ phải ::deep. min-width 0 cho phép co trong ô
   lưới thay vì tràn theo bề rộng nội dung. */
.dsg-grid[b-eilkrqox06]  > * {
    min-width: 0;
}

/* Ô bắt đầu hàng mới (2026-10-09): cặp SỐ | NỀN của một ô lớn luôn cùng hàng dù số ô phía trên lẻ — ô trống còn lại ở
   hàng trước là cố ý. Ở chế độ một cột vô hại (cột 1 là cột duy nhất). */
.dsg-grid[b-eilkrqox06]  > .dsg-row-start {
    grid-column: 1;
}
/* /Components/Shared/DeviceTreePanel.razor.rz.scp.css */
/* Hai khung, mirror khuôn EquipmentTreePage (.eqt-split) — user chốt 2026-09-08 theo hình mẫu SCADA:
   TRÁI = cây điều hướng GỌN (tên + một dấu trạng thái), có chuột phải;
   PHẢI = bảng dữ liệu của nút đang chọn với thanh công cụ. Cây KHÔNG chứa bảng, bảng KHÔNG chứa cây. */

/* ── Chiều cao: khít viewport, CUỘN BÊN TRONG từng khung ──
   Màn giám sát công nghiệp không cuộn cả trang: cây dài thì cuộn trong cây, bảng dài thì cuộn trong bảng,
   thanh công cụ và tiêu đề đứng yên. Trước bản này panel cao theo nội dung ⇒ mới vào thì trống nửa dưới,
   nhiều điểm thì đẩy cả trang ra thanh cuộn (user báo 2026-09-08).

   Chiều cao do KHUÔN TRANG đặt (2026-10-03): thẻ gốc mang `page-fill-fit` ⇒ từ lg nó lấp đúng vùng tab của trang
   `page-fill` bằng chuỗi flex (app.css, Responsive-Layout-Standard mục "Trang lấp đầy màn hình"). Bỏ hẳn bản cũ
   `height: calc(100dvh - --ft-fill-chrome)` + JS `ftFillHeight` đo khoảng chừa phía trên — bản đó phải đo lại mỗi khi
   thứ phía trên đổi chiều cao và từng rớt xuống sàn 420px khi đo trúng lúc màn chờ chưa gỡ. Lý do cũ không ép cả chuỗi
   ("6 tab còn lại chảy tự nhiên") hết đúng: user chốt MỌI tab khít màn hình, tab dài cuộn trong thẻ.
   Khung KHÔNG tự đặt chiều cao: ngoài khuôn `page-fill` (hoặc dưới lg) nó cao theo nội dung — cuộn trang. */
.dtree-root[b-j6armpmyzx] {
    display: flex;
    flex-direction: column;
    min-height: 420px;   /* màn thấp: thà cuộn trang còn hơn bóp khung còn vài dòng */
}

/* Thanh công cụ đầu panel: cao theo nội dung (có thể mọc thêm dòng cảnh báo) — flex hấp thụ, không ai phải cộng tay
   chiều cao của nó. Đó là lý do dùng flex thay vì đặt height thẳng lên .dtree-split.
   ::deep vì class này nằm trên MudPaper — component con không mang thuộc tính scope, viết trần là rule CHẾT
   (im lặng: không lỗi biên dịch, chỉ sai bố cục). Kiểm bằng cách grep selector trong obj/**/scopedcss/bundle. */
.dtree-root[b-j6armpmyzx]  .dtree-head {
    flex: 0 0 auto;
}

.dtree-split[b-j6armpmyzx] {
    flex: 1 1 auto;
    min-height: 0;   /* ⚠️ BẮT BUỘC: mặc định min-height của flex item là auto ⇒ con tràn ra ngoài thay vì cuộn */
    display: grid;
    grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
    gap: 12px;
    align-items: stretch;   /* hai khung cao bằng nhau và bằng vùng còn lại */
}

/* Khung TRÁI tự cuộn trọn (cây không có phần đầu cần đứng yên). min-height:0 lặp lại vì grid item
   cũng mặc định min-height:auto. */
.dtree-tree[b-j6armpmyzx] {
    min-height: 0;
    overflow: auto;
}
/* ⚠️ `align-items: stretch` chỉ kéo cái DIV bọc này cao hết khung — THẺ TRẮNG bên trong vẫn cao theo nội
   dung, nên cây ít dòng thì thẻ dừng ngay sau dòng cuối và lộ nền trang bên dưới (user báo bằng ảnh
   2026-09-09). Khung PHẢI không dính vì thẻ của nó đã có `flex: 1 1 auto`.
   Dùng `min-height: 100%` chứ KHÔNG dùng `height: 100%` hay `align-items: stretch`: cây dài hơn khung thì
   thẻ phải CAO HƠN khung để div bọc cuộn — ép đúng 100% là nền thẻ cắt ngang giữa các dòng đang cuộn.
   ::deep vì `.mud-paper` là root của component con, không mang thuộc tính scope (bẫy đã trả giá 3 lần). */
.dtree-tree[b-j6armpmyzx]  > .mud-paper {
    min-height: 100%;
    box-sizing: border-box;   /* pa-2 nằm TRONG 100%, nếu không sẽ luôn dư ra một thanh cuộn dọc */
}

/* Khung PHẢI: thẻ chiếm trọn chiều cao và là CỘT FLEX — tiêu đề + thanh tìm kiếm đứng yên,
   chỉ .dtree-scroll bên trong cuộn. ::deep vì .mud-paper do component con render (không mang scope). */
.dtree-detail[b-j6armpmyzx] {
    min-height: 0;
    display: flex;
}
.dtree-detail[b-j6armpmyzx]  > .mud-paper {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.dtree-scroll[b-j6armpmyzx] {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

/* Màn hẹp (< breakpoint `lg` của MudBlazor — Responsive-Layout-Standard §1.2, không tự chế số):
   xếp dọc và TRẢ chiều cao về tự nhiên. Ở khổ này thanh AppBar đổi chiều cao (64 → 56 → 48) và còn
   thêm thanh điều hướng đáy 64px, nên mọi con số trừ đi đều sai; cuộn trang là hành vi đúng của mobile. */
@media (max-width: 1279.98px) {
    .dtree-root[b-j6armpmyzx] {
        height: auto;
        min-height: 0;
    }
    .dtree-split[b-j6armpmyzx] { grid-template-columns: minmax(0, 1fr); }
    .dtree-tree[b-j6armpmyzx],
    .dtree-detail[b-j6armpmyzx],
    .dtree-scroll[b-j6armpmyzx] {
        overflow: visible;
        min-height: 0;
    }
    .dtree-detail[b-j6armpmyzx]  > .mud-paper { display: block; }
    /* Khổ này khung không có chiều cao xác định nên `min-height: 100%` vô nghĩa — trả thẻ về cao theo
       nội dung, đúng hành vi cuộn trang của mobile. */
    .dtree-tree[b-j6armpmyzx]  > .mud-paper { min-height: 0; }
}

/* Dòng cây: tên cắt đuôi, dấu trạng thái dồn phải. Hàng phải nhận chuột phải nên chiếm trọn bề ngang. */
.dtree-row[b-j6armpmyzx] {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    width: 100%;
}
.dtree-row .dtree-name[b-j6armpmyzx] {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dtree-row .dtree-meta[b-j6armpmyzx] {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: .75rem;
    opacity: .75;
    white-space: nowrap;
}

/* Mã trên dây — monospace để mắt tách mã khỏi tên. ::deep vì gắn lên MudText (component con). */
[b-j6armpmyzx] .mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .8em;
}

/* ── Bảng tag của một điểm (bước 3) ── */
/* Hàng tiêu đề nhóm (thư mục tag) + ô giá trị. ::deep vì <tr>/<td> nằm trong MudSimpleTable — cùng lý do .mono. */
.dtree-root[b-j6armpmyzx]  tr.dtree-tag-group > td {
    background: var(--mud-palette-background-gray);
    font-weight: 600;
    font-size: .8rem;
    padding-top: 4px;
    padding-bottom: 4px;
}
.dtree-root[b-j6armpmyzx]  td.dtree-tag-value {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
/* Ô chưa có giá trị kể từ khi chọn điểm — mờ đi để mắt không đọc dấu gạch như một con số. */
.dtree-root[b-j6armpmyzx]  td.dtree-tag-value.dtree-tag-empty {
    opacity: .5;
}
/* Dải trạng thái kênh trực tiếp phía trên bảng tag. */
/* Bộ chuyển chế độ xem của điểm (Tag/Tổng quan/Chẩn đoán) — MudButtonGroup mặc định co theo chữ mỗi nút
   (chữ dài "Chẩn đoán" làm nút đó rộng hơn hẳn 2 nút kia).
   ⚠️ `.mud-button-group-root` KHÔNG mang thuộc tính scope (đó là gốc do MudButtonGroup — component con —
   tự vẽ), nên chọn qua `::deep` từ `.dtree-view-switch` (div bọc ngoài do CHÍNH file này vẽ). Từng thử
   gắn class thẳng lên MudButtonGroup qua Class="..." — không ăn, vì lúc đó .dtree-view-switch lại là
   phần tử KHÔNG scope, ::deep phía sau nó vô nghĩa.
   ⚠️ Bản đầu dùng flex + flex-grow:1 — chia đều THẬT nhưng nút group tự giãn lấp hết bề ngang của khối cha
   (khối cha ở đây rất rộng), 3 nút bị kéo to lố. Grid với cột `1fr` + container `width:fit-content` cho
   ĐÚNG THỨ CẦN: mỗi cột lấy đúng bề rộng nội dung nút RỘNG NHẤT (không hơn), cụm không giãn quá nội dung
   của nó. Đã đo trực tiếp trên DOM thật trước khi chốt: 3 nút ra cùng ~98px, cụm chỉ rộng ~291px dù khối
   cha rộng 920px — không phải suy luận trên giấy. `grid-auto-columns` (không phải `grid-template-columns`
   cố định N cột) vì số nút đổi theo quyền xem Chẩn đoán (2 hoặc 3 nút). */
.dtree-view-switch[b-j6armpmyzx]  .mud-button-group-root {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    width: fit-content;
}

.dtree-view-switch[b-j6armpmyzx]  .mud-button-group-root > button {
    min-width: 0;
}

.dtree-live[b-j6armpmyzx] {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 8px;
}

/* Chip Live (Paused/Waiting icon-only, 2026-09-18) — icon lệch trái vì MudBlazor luôn gắn margin bất đối
   xứng cho icon chip (-4px trái / +4px phải, để chừa khe trước chữ nhãn). Chip rỗng chữ thì phần khe đó
   chỉ còn đẩy icon lệch, không có chữ để chừa chỗ cho. Huỷ margin đó CHỈ khi span nhãn rỗng (:has, không
   đụng 3 trạng thái còn chữ — Live/Silent/Disconnected — nơi khe đó vẫn cần để icon không dính chữ). Đo
   trực tiếp trên DOM thật trước khi chốt: lệch 4px trái/12px phải → sau khi huỷ margin, đều 8px/8px. */
.dtree-live[b-j6armpmyzx]  .mud-chip:has(.mud-chip-content:empty) svg {
    margin: 0;
}

/* ── Ô nhập tên tại chỗ (2026-09-09) ───────────────────────────────────────────
   Dùng chung cho tiêu đề nhóm, dòng cây và ô nhãn tag. Phải THẤP hơn ô nhập mặc định của MudBlazor:
   dòng cây ở chế độ Dense chỉ cao ~28px, thả nguyên ô nhập vào là hàng giãn gấp đôi rồi cả cây nhảy
   một nhịp khi bắt đầu sửa. `::deep` là bắt buộc — phần tử bên trong do component con vẽ nên không
   mang thuộc tính scope của file này (cùng bài học .eqt-props / .mpl-rule). */
.dtree-rename[b-j6armpmyzx] {
    flex: 1 1 auto;
    min-width: 0;
}
.dtree-rename[b-j6armpmyzx]  .mud-input-control {
    margin-top: 0;
}
.dtree-rename[b-j6armpmyzx]  .mud-input-slot {
    height: 26px;
    font-size: .8125rem;
    padding-top: 0;
    padding-bottom: 0;
}
.dtree-rename[b-j6armpmyzx]  .mud-input > input {
    padding-top: 2px;
    padding-bottom: 2px;
}

/* Bề rộng cột CỐ ĐỊNH của bảng tag một điểm (2026-09-17) — giá trị sống đổi độ dài chữ mỗi bản tin, để bảng tự co theo
   nội dung là cả bảng xê ngang một nhịp. Cột Nhãn (không class) nhận phần còn lại; min-width để màn hẹp cuộn ngang
   trong .dtree-scroll thay vì bóp cột Nhãn về 0. ::deep vì <table>/<col> do MudSimpleTable vẽ. */
.dtree-scroll[b-j6armpmyzx]  .dtree-tagtable table {
    table-layout: fixed;
    width: 100%;
    /* Tổng bề rộng px khai bên dưới là 882 — min-width phải chừa thêm chỗ THẬT cho cột Nhãn (ô chứa
       MudTextField đổi tên tại chỗ). Số cũ 920 chỉ chừa 38px ⇒ ô đổi tên bẹp dí ở màn hẹp. */
    min-width: 1040px;
}
/* Tiêu đề KHÔNG xuống dòng: các cột px hẹp vừa đủ cho nhãn tiếng Việt, mà nhãn dịch dài hơn ("IO type",
   "Updated") sẽ gãy 2 hàng hoặc tràn — table-layout:fixed không cho cột tự nới, nên đây là lớp lỗi chỉ
   hỏng ở MỘT culture, bản tiếng Việt test không bao giờ thấy. */
.dtree-scroll[b-j6armpmyzx]  .dtree-tagtable th {
    white-space: nowrap;
}
.dtree-scroll[b-j6armpmyzx]  col.dtree-col-tag { width: 96px; }
.dtree-scroll[b-j6armpmyzx]  col.dtree-col-kind { width: 104px; }
.dtree-scroll[b-j6armpmyzx]  col.dtree-col-storage { width: 72px; }
.dtree-scroll[b-j6armpmyzx]  col.dtree-col-unit { width: 72px; }
.dtree-scroll[b-j6armpmyzx]  col.dtree-col-io { width: 72px; }
.dtree-scroll[b-j6armpmyzx]  col.dtree-col-address { width: 140px; }
.dtree-scroll[b-j6armpmyzx]  col.dtree-col-value { width: 150px; }
.dtree-scroll[b-j6armpmyzx]  col.dtree-col-time { width: 88px; }
.dtree-scroll[b-j6armpmyzx]  col.dtree-col-actions { width: 88px; }
/* Chữ dài hơn cột thì cắt bằng dấu … (title trên ô giữ bản đầy đủ) thay vì đẩy cột rộng ra. */
.dtree-scroll[b-j6armpmyzx]  td.dtree-cell-clip,
.dtree-scroll[b-j6armpmyzx]  td.dtree-tag-value {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Nút sửa nhãn tag: mờ sẵn để không át bảng số, rõ hẳn khi rê vào hàng hoặc khi đi bằng bàn phím.
   ⛔ PHẢI đi qua ::deep — class nằm trên `MudIconButton`, tức trên ROOT của một component con, mà root đó
   KHÔNG mang thuộc tính scope của file này (cùng bẫy .eqt-props / .mpl-rule / .dtree-tree đã trả giá).
   Neo vào `.dtree-scroll` là div THUẦN nên nó có scope, ::deep đi tiếp xuống bên trong. */
.dtree-scroll[b-j6armpmyzx]  .dtree-tagedit {
    opacity: .45;
    transition: opacity .15s;
}
.dtree-scroll[b-j6armpmyzx]  tr:hover .dtree-tagedit,
.dtree-scroll[b-j6armpmyzx]  .dtree-tagedit:focus-visible {
    opacity: 1;
}

/* ── Dải chỉ số của nút vị trí (2026-09-09) ────────────────────────────────────
   Tự xuống dòng theo bề ngang khung phải: khung này co giãn theo cửa sổ nên gõ cứng số cột là
   vỡ ở màn hẹp. minmax + auto-fit để 4 ô xếp một hàng khi rộng, gập dần khi hẹp. */
.dtree-kpi[b-j6armpmyzx] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 8px;
}
.dtree-kpi-cell[b-j6armpmyzx] {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
    padding: 8px 10px;
    min-width: 0;
}

/* Payload mẫu trong thẻ điểm (2026-09-10) — cùng hình với khối mẫu ở bảng hợp đồng để hai chỗ trông như
   một thứ. Cuộn NGANG riêng: JSON một dòng dài không được đẩy cả khung phải ra thanh cuộn. */
.dtree-payload[b-j6armpmyzx] {
    background: var(--mud-palette-background-gray);   /* "gray" chu KHONG "grey": MudBlazor 9 chi dinh nghia bien nay (kiem: grep --mud-palette-.*gr[ae]y trong staticwebassets). Viet "grey" la bien khong ton tai => nen trong suot, im lang. */
    padding: 8px 12px;
    border-radius: var(--mud-default-borderradius);
    overflow-x: auto;
    font-size: .8rem;
    margin: 0;
}

/* Chu giai ma so duoi payload mau: chu nho mau phu, khong tranh su chu y voi khoi JSON. */
.dtree-enum-legend[b-j6armpmyzx] {
    margin-top: 4px;
    font-size: .75rem;
    color: var(--mud-palette-text-secondary);
}
/* /Components/Shared/FtTablePager.razor.rz.scp.css */
/* Dropdown số dòng/trang — gọn, nhẹ cho cân đối với hàng phân trang (2026-06-28).
   Dùng Variant.Text (không viền) + cỡ chữ nhỏ. ::deep để chạm DOM nội bộ MudSelect. */
.ft-table-pager[b-6ulhp7pvh8]  .ft-pager-select .mud-input-control {
    font-size: 0.8125rem;
}

/* Chân thẻ bảng (Footer="true", khuôn .page-fill-table — Responsive-Layout-Standard §16): cùng nền thẻ, viền trên mảnh
   thay cho khoảng trống mt-3; thẻ bọc (app.css) giữ nó flex:none ở đáy. */
.ft-table-pager-footer[b-6ulhp7pvh8] {
    padding: 8px 16px;
    border-top: 1px solid var(--mud-palette-table-lines);
    background: var(--mud-palette-surface);
}
/* /Components/Shared/GenericCard.razor.rz.scp.css */
/* Thẻ Generic / Hệ phụ trợ (2026-10-04): khung chung (vỏ, dải đầu, lưới ô, ô cảnh báo) ở DashboardCard.razor.css. File này chỉ
   còn CSS cho hai MẢNH riêng mà thẻ tự định nghĩa (scoped CSS bám component định nghĩa markup): thông điệp thay thân + dòng giờ. */

/* Dòng giờ của bản tin cuối ở chân thẻ */
.device-timestamp[b-xxqjftn3de] {
    text-align: center;
    font-size: 0.7rem;
    color: var(--mud-palette-text-disabled);
    margin-top: 8px;
    line-height: 1;
}

/* Thông điệp thay thân: chưa gán profile / chưa có chỉ tiêu / chưa chọn ô */
.device-nodata[b-xxqjftn3de] {
    text-align: center;
    color: var(--mud-palette-text-disabled);
    padding: 24px 0;
    font-size: 0.9rem;
}
/* /Components/Shared/MachineCard.razor.rz.scp.css */
/* Thẻ Máy (2026-10-04): khung chung (vỏ, dải đầu, lưới ô, ô cảnh báo, biến thể gọn `dc-compact`, dải đầu nháy `dc-header-alert`,
   lớp giá trị `dc-value--warn/--high`) ở DashboardCard.razor.css. File này chỉ còn CSS cho các MẢNH riêng mà thẻ tự định nghĩa
   (scoped CSS bám component định nghĩa markup): huy hiệu dải đầu, Lệnh SX / Sản lượng / Thời gian / KPI, hàng nút.
   Biến --dash-* = kiểu màu thẻ admin đặt (DashboardCardDisplay.StyleVars) — mọi chỗ đọc đều kèm màu theme dự phòng. */

/* ── Huy hiệu dải đầu ── */
.header-badges[b-wid7xjqbjx] {
    display: flex;
    align-items: center;
    gap: 4px;
}
.device-status-badge[b-wid7xjqbjx] {
    font-size: 0.68rem;
    font-weight: 600;
    color: #fff;
    background: rgba(255,255,255,0.2);
    padding: 1px 6px;
    border-radius: 10px;
    white-space: nowrap;
    flex-shrink: 0;
}

/* ── ACTUAL vs PLAN ── */
.actual-plan-row[b-wid7xjqbjx] {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    margin-bottom: 3px;
}
.actual-box[b-wid7xjqbjx], .plan-box[b-wid7xjqbjx] {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 4px 4px;
    border-radius: 6px;
}
/* Nền RIÊNG (--dash-actual-bg / --dash-target-bg, 2026-10-09) vắng thì pha 12% từ màu số như trước — tỉ lệ khớp
   DashboardCardDisplay.TintHex (ô màu của bảng cấu hình hiện đúng màu này). */
.actual-box[b-wid7xjqbjx] { background: var(--dash-actual-bg, color-mix(in srgb, var(--dash-actual, var(--mud-palette-success)) 12%, var(--dash-card-bg, var(--mud-palette-surface)))); }
.plan-box[b-wid7xjqbjx] { background: var(--dash-target-bg, color-mix(in srgb, var(--dash-target, var(--mud-palette-error)) 12%, var(--dash-card-bg, var(--mud-palette-surface)))); }
.actual-box .big-number[b-wid7xjqbjx] {
    color: var(--dash-actual, var(--mud-palette-success));
    font-size: 1.4rem;
    font-weight: 800;
    font-family: 'Roboto Mono', 'Consolas', monospace;
    line-height: 1.1;
}
.plan-box .big-number[b-wid7xjqbjx] {
    color: var(--dash-target, var(--mud-palette-error));
    font-size: 1.4rem;
    font-weight: 800;
    font-family: 'Roboto Mono', 'Consolas', monospace;
    line-height: 1.1;
}
.box-label[b-wid7xjqbjx] {
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    color: var(--dash-label, var(--mud-palette-text-secondary));
    letter-spacing: 0.5px;
}

/* ── Time row ── */
.time-row[b-wid7xjqbjx] {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4px;
    margin-bottom: 2px;
}
.time-item[b-wid7xjqbjx] {
    display: flex;
    align-items: center;
    gap: 3px;
    padding: 2px 6px;
    border-radius: 4px;
    background: var(--dash-cell-bg, var(--mud-palette-background-gray));
    font-size: 0.75rem;
}
.time-label[b-wid7xjqbjx] {
    flex: 1;
    font-size: 0.7rem;
    color: var(--dash-label, var(--mud-palette-text-secondary));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.time-value[b-wid7xjqbjx] {
    font-weight: 700;
    font-family: 'Roboto Mono', 'Consolas', monospace;
    color: var(--dash-time-value, var(--mud-palette-primary));
    font-size: 0.82rem;
}

/* ── OEE / MTBF / MTTR — cùng vẻ với ô thông số gọn (nhãn trên, số dưới), kẻ ngăn trên vì đứng dưới lưới ô ── */
.kpi-row[b-wid7xjqbjx] {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 4px;
    margin-top: 4px;
    border-top: 1px solid var(--mud-palette-lines-default);
    padding-top: 4px;
}
.kpi-item[b-wid7xjqbjx] {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 3px 2px;
    border-radius: 4px;
    background: var(--dash-cell-bg, var(--mud-palette-background-gray));
}
.kpi-label[b-wid7xjqbjx] {
    font-size: 0.62rem;
    color: var(--dash-label, var(--mud-palette-text-disabled));
    text-transform: uppercase;
    letter-spacing: 0.3px;
}
.kpi-value[b-wid7xjqbjx] {
    font-size: 0.92rem;
    font-weight: 700;
    font-family: 'Roboto Mono', 'Consolas', monospace;
    color: var(--dash-value, var(--mud-palette-text-primary));
    line-height: 1.2;
}

/* ── Actions row ── khe chia đều, nút chiếm hết khe (xem comment trong .razor).
   grid-template-columns đặt INLINE ở markup vì số khe tính theo cấu hình từng máy. */
.card-actions[b-wid7xjqbjx] {
    display: grid;
    gap: 4px;
    margin-top: 4px;
}
.card-actions .action-slot[b-wid7xjqbjx] {
    display: flex;
}
/* ::deep vì .mud-button-root do MudButton render (component con, scoped CSS không chạm được).
   width:100% → bề rộng theo KHE, không theo độ dài nhãn ("Gọi" và "Xử lý" cùng rộng).
   min-height 36px = mức touch-bump repo đang dùng cho ô thao tác bảng; trước đây nút để
   min-height:0 + padding 2px nên vùng bấm quá nhỏ trên màn HMI. */
.card-actions[b-wid7xjqbjx]  .mud-button-root {
    width: 100%;
    min-width: 0;
    min-height: 36px;
    font-size: 0.72rem;
    padding: 2px 4px;
}

/* ── Huy hiệu Andon nháy khi Andon MỞ ── */
.andon-badge-pulse[b-wid7xjqbjx] {
    animation: andon-pulse-b-wid7xjqbjx 1.5s ease-in-out infinite;
}
@keyframes andon-pulse-b-wid7xjqbjx {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}
/* Người bật "giảm chuyển động": huy hiệu đứng yên — dải đầu đã có nền Andon tĩnh + viền dày (luật ở DashboardCard.razor.css). */
@media (prefers-reduced-motion: reduce) {
    .andon-badge-pulse[b-wid7xjqbjx] {
        animation: none;
    }
}
/* /Components/Shared/ParamLabelEditor.razor.rz.scp.css */
/* (Rule `.mpl-preview` căn giữa thẻ xem trước đã gỡ 2026-10-04 — thẻ xem trước nay thuộc DashboardCardDesigner.)

   ⛔ KHÔNG `::deep` cho class đặt trên phần tử của CHÍNH component này. Blazor biên dịch
   `::deep X` thành `[b-scope] X`, tức đòi X có TỔ TIÊN mang scope attribute; mà phần tử viết
   trong markup của component đã tự mang scope, còn tổ tiên nó (div do MudItem/MudGrid render)
   thì KHÔNG ⇒ selector không khớp gì cả và cả khối rule rơi vào hư không — build vẫn xanh, chỉ
   nhìn màn hình mới biết (đã cắn thật 2026-08-15 với khu quy tắc màu). `::deep` chỉ dành cho
   phần tử do component KHÁC render (xem rule cuối file). */

/* ⛔ Siết bề rộng ô nhập của MudColorPicker Ở ĐÂY, KHÔNG đặt Style lên component: style bám gốc
   picker (bao cả bảng chọn màu) sẽ ép bảng màu thành dải dọc không bấm được — bài học Generic
   2026-08-13. `.mud-input-control` chỉ bọc ô nhập, không bọc popover.

   ⚠️ KHÔNG viết `::deep .mpl-xxx .mud-picker …` — kèm class của phần tử-mình vào trước là dính
   đúng cái bẫy ở trên (nó thành `[b-scope] .mpl-xxx …`, đòi .mpl-xxx là CON của phần tử mang
   scope). Để trần `.mud-picker` là khớp, và an toàn vì component này chỉ có một MudColorPicker. */
[b-47sgpuj3hl] .mud-picker .mud-input-control {
    min-width: 0;
}
