/*
  Device hierarchy board (DeviceHierarchyView and the card components underneath it).

  Loaded globally from index.html, the same way the revenue page loads its own sheet. Every rule is
  namespaced with the dhv- prefix because the board spans several components: the view owns the
  bands, DeviceBoardBranch the nesting, and the two card components the cards themselves.
*/

.dhv-root {
    /*
      Card widths. A card carrying its signal tiles puts them beside the figures instead of under them,
      which makes it wide and short rather than narrow and tall — the board has width to spare far more
      often than it has height, because it is scaled to fit the screen rather than scrolled.

      DeviceBoardCardSize mirrors these three figures: it has to predict how big a card comes out in
      order to pick the column layout before anything has been drawn.
    */
    --dhv-card-w: 265px;
    --dhv-card-w-wide: 440px;
    --dhv-card-main-w: 190px;

    --dhv-consume: #0ea5e9;
    --dhv-produce: #10b981;
    /*
      The connection point says which way it is running rather than carrying a colour of its own:
      green while it delivers back to the net, blue while it draws from it. Both are darker than the
      device accents above, so the anchor still reads as the anchor instead of as one more producer
      or consumer. Without a meter, or with the meter sitting at zero, there is no direction to show
      and it falls back to a neutral.
    */
    --dhv-grid-export: #047857;
    --dhv-grid-import: #0369a1;
    --dhv-grid: #64748b;
    --dhv-idle-line: #a9b4c4;
}

/*
  ---- KPI strip ----

  The one row the board keeps for itself, now that the buttons that change it sit up in the page's
  bar. Figures on the left; on the right the two things that only change how the diagram is looked
  at rather than what it says.

  Everything here is sized to hold that one line down to about 1280px, which is the same width at
  which the bar above gives up and stacks. A second row of chrome does not push the board down —
  the board is fitted to whatever height is left over — it makes the board smaller.
*/
.dhv-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    /* Indents itself, because the panel it sits in is flush so the board below can reach the edges. */
    padding: 0.6rem 0.875rem 0;
    margin-bottom: 0.45rem;
}

.dhv-kpis {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.dhv-kpi {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    border: 1px solid var(--rz-border-color);
    border-radius: 8px;
    padding: 0.2rem 0.55rem;
    /* White on the canvas rather than a shade of it: the strip sits on the board's own tint now, and
       a pill filled with a neighbouring grey has nothing but its border to stand on. */
    background: var(--rz-base-background, #fff);
}

.dhv-kpi-label {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.64rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    white-space: nowrap;
    color: var(--rz-text-secondary-color);
}

/*
  These three dots are the key to the whole diagram: the same colours the cards and the flow ribbons
  are drawn in, next to the words they stand for. There used to be a separate legend saying it again
  a few centimetres to the right — a strip of the row wide enough to be what pushed the controls
  onto a second line.
*/
.dhv-kpi-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex-shrink: 0;
}

.dhv-kpi-value {
    display: flex;
    align-items: baseline;
    gap: 0.2rem;
}

.dhv-kpi-value .v {
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}

.dhv-kpi-value .u {
    font-size: 0.7rem;
    font-weight: 600;
    white-space: nowrap;
    color: var(--rz-text-tertiary-color);
}

.dhv-controls {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

/* Drag-to-arrange is the one thing about this board nothing on screen would otherwise suggest, so
   the hint stays even though every other piece of standing explanation has gone. */
.dhv-hint {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    white-space: nowrap;
    font-size: 0.75rem;
    color: var(--rz-text-tertiary-color);
    cursor: default;
}

.dhv-hint .rzi {
    font-size: 1rem;
}

/* Board-wide switch. Lives in the page bar with the buttons, so it only has to match their height. */
.dhv-toggle {
    white-space: nowrap;
    font-size: 0.8125rem;
    color: var(--rz-text-secondary-color);
}

.dhv-toggle label {
    cursor: pointer;
    margin: 0;
    font-size: inherit;
    color: inherit;
}

/* Zoom stays with the board; the percentage between the buttons is a readout, not a field. */
.dhv-zoom {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}

.dhv-zoom-value {
    min-width: 2.75rem;
    text-align: center;
    font-size: 0.72rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--rz-text-secondary-color);
}

/* ---- Board ---- */
.dhv-board {
    position: relative;
    /*
      Claim what is left of the window rather than only as much as the cards happen to need. A site
      diagram is read as one picture, so the board is a screen and the scale adapts to it. Deliberately
      a little short of the full window: nothing here should push the page into scrolling, because the
      wheel over the board is the zoom gesture and there must be nothing left for it to scroll past.
    */
    /* Starting estimate only: once measured, the view pins the exact height that leaves no scroll.
       Allows for the page header, the one bar of dashboard chrome and the KPI strip above it —
       browser-interactions.js space() mirrors this figure. */
    min-height: calc(100vh - 20rem);
    display: flex;
    flex-direction: column;
    justify-content: safe center;
    border: 1px solid var(--rz-border-color);
    border-radius: 18px;
    background: var(--rz-base-50, #f7f9fc);
    padding: 1.25rem 1.5rem 1.75rem;
    box-shadow: 0 1px 2px rgba(16, 40, 80, 0.06), 0 10px 30px rgba(16, 40, 80, 0.06);
}

.dhv-zone {
    position: relative;
    z-index: 1;
}

/*
  The bands carry no colour of their own. Every card already says on its accent edge which way its
  energy is going, so tinting the band behind it only repeats that in a weaker form.
*/
.dhv-zone-produce,
.dhv-zone-consume {
    padding: 0.25rem 0 0.5rem;
}

.dhv-zone-label {
    display: flex;
    justify-content: center;
    margin-bottom: 0.85rem;
}

.dhv-zone-label span {
    border: 1px solid var(--rz-border-color);
    border-radius: 999px;
    background: var(--rz-base-background-color, #fff);
    padding: 0.2rem 0.85rem;
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--rz-text-secondary-color);
}

/*
  A band is one or more columns of branches. Cards carry a fixed width so every column lines up and a
  lone card does not shrink to its text; a branch that fans out sideways is free to be wider.

  Filled column by column, so a band still reads top to bottom before it moves across. The row count
  comes from the view (--dhv-rows), which is the whole mechanism: with one column it is simply the
  number of cards and nothing has changed, and with more it is where each next column starts.

  The rows are declared rather than implicit, so every column shares them and a row is as tall as the
  tallest card in it. That is what keeps the gaps between rows lined up clear across the band — and
  those gaps are not only gaps, they are the channels the flow paths to the far columns run along.
*/
.dhv-zone-grid {
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: repeat(var(--dhv-rows, 1), auto);
    grid-auto-columns: max-content;
    align-items: center;
    justify-items: center;
    justify-content: center;
    row-gap: 2.75rem;
    /* Wider than the row gap: the paths to the columns beyond it run down the middle of it. */
    column-gap: 3.5rem;
}

/*
  The three bands sit side by side and the energy reads left to right: producers into the connection
  point, the connection point out to the consumers. Centred against each other, so the connection
  point lines up with the middle of the columns feeding it rather than hugging the top.

  Sized to its content rather than stretched across the board, which is what lets the board be fitted
  to the screen at all: a grid of 1fr columns is always exactly as wide as the space it is given, so
  there would never be anything to scale up or down towards.
*/
.dhv-bands {
    display: grid;
    grid-template-columns: auto auto auto;
    gap: 3.5rem;
    align-items: center;
    justify-items: center;
    width: max-content;
    /* "safe" so that content wider than the board falls back to starting at the left edge instead of
       being centred half off it, where the overflowing side could not be reached at all. */
    align-self: safe center;
}

/* The middle column stacks: bidirectional cards above, the connection point, then more below. The
   gap is generous because the flow paths between them are short and need room to be seen. */
.dhv-zone-net {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3rem;
}

.dhv-empty-zone {
    text-align: center;
    font-size: 0.78rem;
    color: var(--rz-text-tertiary-color);
    padding: 0.5rem 0 1rem;
}

/* ---- Flow layer ---- */
.dhv-flow-layer {
    position: absolute;
    inset: 0;
    overflow: visible;
    pointer-events: none;
    z-index: 0;
}

/*
  A ribbon rather than a wire. Its width is how much the path carries, so the board reads as volumes
  at a glance; drawn faintly, because at eighteen pixels a solid line would shout over the cards.
*/
.dhv-flow-line {
    fill: none;
    stroke-linecap: round;
    /* Quick enough to keep up with a zoom gesture; the ribbon should follow the cards, not trail them. */
    transition: stroke-width 0.2s ease, stroke 0.2s ease;
}

/* The dots travelling along a ribbon are what say which way the energy is actually going. */
.dhv-flow-dot {
    opacity: 0.95;
}

@keyframes dhv-shimmer {
    to { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
    /* The ribbon still carries the direction in its arrowhead, so the moving dots can simply go. */
    .dhv-flow-dot { display: none; }
    .dhv-flow-line { transition: none; }
    .dhv-skeleton { animation: none; }
    .dhv-age.is-refreshing { animation: none; }
}

/* ---- Card ---- */
.dhv-card {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0.4rem 0.7rem;
    box-sizing: border-box;
    /* Fixed so every column lines up and a card never shrinks to the width of its own text. */
    width: var(--dhv-card-w);
    flex: 0 0 auto;
    background: var(--rz-base-background-color, #fff);
    border: 1px solid var(--rz-border-color);
    border-radius: 14px;
    padding: 0.7rem 0.8rem 0.65rem 1.1rem;
    cursor: grab;
    box-shadow: 0 1px 2px rgba(16, 40, 80, 0.06), 0 6px 16px rgba(16, 40, 80, 0.06);
    transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease, opacity 0.18s ease;
}

.dhv-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 10px rgba(16, 40, 80, 0.09), 0 10px 24px rgba(16, 40, 80, 0.08);
}

.dhv-card:active { cursor: grabbing; }

/*
  Tiles beside the figures instead of under them. Two panes rather than one taller column: the header,
  the primary value and the footer already occupy a fixed amount of height that the tiles can sit
  alongside for free, so the card ends up both shorter and smaller in area than the stacked version.

  The width follows the switch and the panes follow the readings, which are deliberately two different
  things. Every card in a band has to be the same width or its columns stop lining up, but a device that
  only reports its power has no tiles to put beside its figures — so that card is just as wide and gives
  the whole width to its figures instead.
*/
.dhv-card.is-landscape { width: var(--dhv-card-w-wide); }

.dhv-card.has-tiles { grid-template-columns: var(--dhv-card-main-w) minmax(0, 1fr); }

.dhv-card-main {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    min-width: 0;
}

/* Centred against the figures beside it, so a card with one row of tiles does not look top-heavy. */
.dhv-card-side {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-width: 0;
}

/*
  Rows that belong to the card as a whole rather than to either pane. The chips are the reason this
  matters: spanning both panes gives them twice the width, so a group of ten members needs half the
  rows of them. A device card keeps its footer inside the left pane, under the figures it acts on; a
  group card's spans the card, under the chips.
*/
.dhv-card > .dhv-chip-row,
.dhv-card > .dhv-empty-group,
.dhv-card > .dhv-card-foot,
.dhv-card > .dhv-expand { grid-column: 1 / -1; }

/* An expanded group holds full member cards, so it may outgrow the standard card width. */
.dhv-card.is-group.is-expanded { width: auto; max-width: 640px; }

/* A group is picked up as one card, like a device. Only cards a group positions itself sit still. */
.dhv-card.is-static { cursor: default; }

.dhv-card.is-root {
    border-color: color-mix(in srgb, var(--dhv-accent, var(--rz-primary)) 55%, var(--rz-border-color));
    box-shadow:
        0 0 0 4px color-mix(in srgb, var(--dhv-accent, var(--rz-primary)) 12%, transparent),
        0 10px 28px rgba(16, 40, 80, 0.14);
}

/*
  Everything on the board hangs off the connection point, so it is the one card that should read
  as the anchor: a deeper shade than the devices, a heavier accent edge and a deeper lift.
*/
.dhv-card.is-grid {
    border-color: color-mix(in srgb, var(--dhv-grid) 45%, var(--rz-border-color));
    background: linear-gradient(180deg, color-mix(in srgb, var(--dhv-grid) 6%, var(--rz-base-background-color, #fff)),
        var(--rz-base-background-color, #fff));
    box-shadow:
        0 0 0 4px color-mix(in srgb, var(--dhv-grid) 14%, transparent),
        0 12px 32px color-mix(in srgb, var(--dhv-grid) 22%, transparent);
}

.dhv-card.is-grid .dhv-accent-bar { width: 7px; }

/*
  Which way the meter runs colours the whole card, not just its edge: border, wash, lift and icon
  tile all read off --dhv-grid, so re-pointing that one property here swings the anchor from drawing
  to delivering. Negative is delivering back to the net, which is what is-producing stands for.
*/
.dhv-card.is-grid.is-producing { --dhv-grid: var(--dhv-grid-export); }

.dhv-card.is-grid.is-consuming { --dhv-grid: var(--dhv-grid-import); }

.dhv-card.is-dragging {
    opacity: 0.45;
    transform: scale(0.97);
}

.dhv-card.is-drop-target {
    border-color: var(--dhv-consume);
    box-shadow: 0 0 0 3px rgba(20, 184, 166, 0.25), 0 8px 22px rgba(20, 184, 166, 0.18);
    transform: translateY(-2px);
}

/*
  Every card takes every other one — a matching type joins the group, anything else hangs
  underneath — so the only card that reads as unavailable during a drag is one that already hangs
  under the card being dragged, which can never become its parent.
*/
.dhv-card.is-invalid-target {
    opacity: 0.4;
    filter: saturate(0.4);
}

.dhv-card.is-invalid-target:hover { transform: none; }

/*
  The same gesture either adds a device to a group or hangs it underneath, so the card being hovered
  says which one it is offering before the drop commits to it.
*/
.dhv-drop-hint {
    position: absolute;
    top: -0.65rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 3;
    border-radius: 999px;
    padding: 0.16rem 0.6rem;
    background: var(--dhv-consume);
    color: #fff;
    font-size: 0.6rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space: nowrap;
    pointer-events: none;
    box-shadow: 0 3px 10px rgba(14, 165, 233, 0.35);
}

/*
  The accent edge says which way the energy is going right now, not what kind of device this is:
  blue draws, green delivers, grey is idle or silent. The icon tile keeps the device type's own
  colour, so the card still tells you at a glance what it is as well as what it is doing.
*/
.dhv-accent-bar {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 5px;
    border-radius: 14px 0 0 14px;
    background: var(--dhv-idle-line);
    transition: background 0.3s ease;
}

.dhv-card.is-consuming .dhv-accent-bar { background: var(--dhv-consume); }

.dhv-card.is-producing .dhv-accent-bar { background: var(--dhv-produce); }

/* The connection point says the same thing in its own darker shades, delivering included. */
.dhv-card.is-grid .dhv-accent-bar { background: var(--dhv-grid); }

.dhv-card-glow {
    position: absolute;
    inset: 0;
    border-radius: 13px;
    pointer-events: none;
    opacity: 0;
    background: radial-gradient(circle at 50% 0%, rgba(20, 184, 166, 0.16), transparent 70%);
    transition: opacity 0.18s ease;
}

.dhv-card.is-drop-target .dhv-card-glow { opacity: 1; }

.dhv-card-head {
    display: flex;
    align-items: center;
    gap: 0.55rem;
}

.dhv-icon-tile {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    border-radius: 10px;
    color: var(--dhv-accent, var(--rz-primary));
    background: color-mix(in srgb, var(--dhv-accent, var(--rz-primary)) 14%, transparent);
}

.dhv-icon-tile .rzi { font-size: 1.15rem; }

.dhv-head-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1;
}

.dhv-name {
    font-weight: 600;
    font-size: 0.85rem;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dhv-type {
    font-size: 0.68rem;
    color: var(--rz-text-secondary-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dhv-status-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    flex-shrink: 0;
    border-radius: 999px;
    padding: 0.15rem 0.5rem;
    font-size: 0.64rem;
    font-weight: 700;
    white-space: nowrap;
}

.dhv-status-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex-shrink: 0;
    background: currentColor;
}

.dhv-status-badge.is-ok {
    color: var(--rz-success);
    background: color-mix(in srgb, var(--rz-success) 12%, transparent);
}

.dhv-status-badge.is-warning {
    color: var(--rz-warning);
    background: color-mix(in srgb, var(--rz-warning) 14%, transparent);
}

.dhv-status-badge.is-alarm {
    color: var(--rz-danger);
    background: color-mix(in srgb, var(--rz-danger) 12%, transparent);
}

.dhv-status-badge.is-idle {
    color: var(--rz-text-tertiary-color);
    background: var(--rz-base-200, rgba(0, 0, 0, 0.04));
}

.dhv-value-row {
    display: flex;
    align-items: baseline;
    gap: 0.3rem;
    min-height: 1.9rem;
}

.dhv-value {
    font-size: 1.85rem;
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 1;
}

.dhv-value.is-empty { color: var(--rz-text-tertiary-color); }

.dhv-value-unit {
    font-size: 0.74rem;
    font-weight: 600;
    color: var(--rz-text-tertiary-color);
}

.dhv-value-delta {
    font-size: 0.7rem;
    color: var(--rz-text-secondary-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dhv-loadbar {
    height: 5px;
    border-radius: 4px;
    background: var(--rz-base-200, #e8edf3);
    overflow: hidden;
}

.dhv-loadbar-fill {
    height: 100%;
    border-radius: 4px;
    background: linear-gradient(90deg, var(--rz-success), #34d399);
    transition: width 0.6s ease;
}

.dhv-kpi-grid,
.dhv-summary-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.3rem 0.45rem;
}

.dhv-kpi-cell,
.dhv-summary-cell {
    display: flex;
    flex-direction: column;
    min-width: 0;
    border-radius: 8px;
    padding: 0.25rem 0.4rem;
    background: var(--rz-base-100, rgba(0, 0, 0, 0.02));
}

.dhv-kpi-k,
.dhv-summary-k {
    font-size: 0.58rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--rz-text-tertiary-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dhv-kpi-v,
.dhv-summary-v {
    font-size: 0.78rem;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dhv-summary-u {
    font-size: 0.64rem;
    font-weight: 600;
    color: var(--rz-text-tertiary-color);
}

.dhv-kpi-v.is-critical,
.dhv-summary-v.is-critical { color: var(--rz-danger); }

/*
  A tile that must never be cut off, such as the configured kW band: it spans the full card and
  keeps its value intact rather than ellipsising it.
*/
.dhv-kpi-cell.is-wide { grid-column: 1 / -1; }

.dhv-kpi-cell.is-wide .dhv-kpi-v {
    overflow: visible;
    text-overflow: clip;
}

/*
  ---- Hierarchy: a card and whatever hangs underneath it ----

  Children sit in the direction their band's energy travels, so a chain always reads the same way as
  the board does. In the producing column the energy runs towards the connection point, so a child
  sits left of its parent; in the consuming column it runs away from it, so a child sits right. In
  the middle column, where the flow is vertical, children stack underneath instead.
*/
/*
  The gap between a parent and its children is where the flow line is drawn, so it has to be wide
  enough for that line to be a thing you can see rather than a nick between two touching cards.
*/
.dhv-branch {
    display: flex;
    align-items: center;
    gap: 4.5rem;
    min-width: 0;
}

.dhv-branch.is-upstream { flex-direction: row-reverse; }

.dhv-branch.is-downstream { flex-direction: row; }

.dhv-branch.is-stacked { flex-direction: column; }

/*
  Children sit one level deeper than their parent, with enough room above them for the flow path
  to be drawn. Energy runs one way: a nested card is fed by the card above it and never by the
  Net as well, and that line is what says so.
*/
/* Several children of one card line up across the branch's direction of travel. */
.dhv-children {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 2.25rem;
}

.dhv-branch.is-upstream > .dhv-children,
.dhv-branch.is-downstream > .dhv-children { flex-direction: column; }

.dhv-branch.is-stacked > .dhv-children { flex-direction: row; }

.dhv-children .dhv-card {
    box-shadow: 0 1px 2px rgba(16, 40, 80, 0.05);
}

/* ---- Skeletons: the structure renders immediately, values arrive per batch ---- */
.dhv-skeleton {
    display: block;
    border-radius: 6px;
    background: linear-gradient(90deg,
        var(--rz-base-200, #e8edf3) 25%,
        var(--rz-base-100, #f2f5f9) 37%,
        var(--rz-base-200, #e8edf3) 63%);
    background-size: 200% 100%;
    animation: dhv-shimmer 1.4s ease-in-out infinite;
}

.dhv-skeleton-value { width: 6.5rem; height: 1.6rem; }
.dhv-skeleton-key { width: 3.5rem; height: 0.5rem; margin: 0.12rem 0; }
.dhv-skeleton-val { width: 2.75rem; height: 0.62rem; margin: 0.12rem 0; }

.dhv-age {
    font-size: 0.6rem;
    font-weight: 600;
    color: var(--rz-text-tertiary-color);
    padding-left: 0.15rem;
    white-space: nowrap;
}

.dhv-age.is-refreshing {
    animation: dhv-pulse 1.1s ease-in-out infinite;
}

@keyframes dhv-pulse {
    50% { opacity: 0.35; }
}

/* ---- Group chips: one compact tile per member, status carried by the accent edge ---- */
.dhv-chip-row {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
    gap: 0.35rem;
}

.dhv-chip {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.05rem;
    min-width: 0;
    overflow: hidden;
    border-radius: 8px;
    padding: 0.3rem 0.45rem 0.3rem 0.6rem;
    background: var(--rz-base-100, rgba(0, 0, 0, 0.022));
    border: 1px solid var(--rz-border-color);
    transition: background 0.15s ease, border-color 0.15s ease;
}

/*
  The status colours a slim edge rather than the whole tile: with a dozen members on one card a
  full colour wash turns into noise, while an edge still reads at a glance.
*/
.dhv-chip::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: currentColor;
}

.dhv-chip:hover {
    background: var(--rz-base-200, rgba(0, 0, 0, 0.045));
    border-color: color-mix(in srgb, currentColor 35%, var(--rz-border-color));
}

.dhv-chip-code {
    font-size: 0.6rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--rz-text-tertiary-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dhv-chip-v {
    font-size: 0.74rem;
    font-weight: 600;
    color: var(--rz-text-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dhv-chip.is-ok { color: var(--rz-success); }

.dhv-chip.is-warning { color: var(--rz-warning); }

.dhv-chip.is-alarm { color: var(--rz-danger); }

.dhv-chip.is-off,
.dhv-chip.is-neutral { color: var(--rz-text-tertiary-color); }

/* The distribution chips above a large group are counts, so the number leads. */
.dhv-chip-row.is-summary .dhv-chip-v {
    font-size: 0.85rem;
    font-weight: 700;
    color: currentColor;
}

.dhv-empty-group {
    font-size: 0.7rem;
    color: var(--rz-text-tertiary-color);
}

/* ---- Group expansion (grows the card, never scrolls inside it) ---- */
.dhv-expand {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.dhv-expand-divider {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    border-top: 1px dashed var(--rz-border-color);
    padding-top: 0.45rem;
    font-size: 0.64rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--rz-text-tertiary-color);
}

/* The members are full cards, so they get their own grid and the group card grows to fit. */
.dhv-member-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(215px, 290px));
    gap: 0.6rem;
    align-items: start;
    justify-content: center;
}

/* Members fill their own track rather than the board's standard card width. */
.dhv-member-grid .dhv-card {
    width: auto;
    box-shadow: 0 1px 2px rgba(16, 40, 80, 0.05);
}

/*
  A member sits in a track a good deal narrower than a card on the board, so there is no width to put
  its tiles beside the figures — inside a group they stack again.
*/
.dhv-member-grid .dhv-card.has-tiles { grid-template-columns: minmax(0, 1fr); }

/* ---- Card footer ---- */
.dhv-card-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.4rem;
    margin-top: auto;
}

.dhv-flags {
    display: flex;
    align-items: center;
    gap: 0.2rem;
}

.dhv-foot-right {
    display: flex;
    align-items: center;
    gap: 0.2rem;
    flex-shrink: 0;
}

.dhv-badge { font-size: 0.9rem; }
.dhv-badge-warning { color: var(--rz-warning); }
.dhv-badge-info { color: var(--rz-info); }
.dhv-badge-success { color: var(--rz-success); }

/* ---- Responsive: stack the zones and drop the flow layer on narrower screens ---- */
/*
  The bands no longer need a column count per breakpoint: auto-fit already drops a track when the
  board narrows. What still has to change is the flow layer, which needs the three-band geometry to
  make sense, and the middle band, which stops flanking the connection point once it cannot.
*/
/*
  Zooming out buys back a great deal of width, so the three columns survive far narrower screens
  than they used to: the fit scale shrinks the cards until they fit rather than the layout giving up
  on them. Only below this is there too little room for three cards at any readable scale.
*/
@media (max-width: 1099.98px) {
    .dhv-flow-layer { display: none; }
    .dhv-bands { grid-template-columns: minmax(0, 1fr); gap: 1.25rem; width: auto; }
    /* Stacked bands are read by scrolling, so a full-height board would only add empty space. */
    .dhv-board { min-height: 0; display: block; }
    /*
      Splitting a band across columns buys width back on a screen that has width to spare. Here there
      is none, and the board is scrolled rather than fitted, so a band goes back to being one column
      however many the view worked out would fit.
    */
    .dhv-zone-grid {
        grid-auto-flow: row;
        grid-template-rows: none;
        grid-template-columns: minmax(0, 1fr);
        row-gap: 1.5rem;
    }
}

@media (max-width: 899.98px) {
    .dhv-member-grid { grid-template-columns: minmax(0, 1fr); }
    .dhv-chip-row { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    /* Too narrow to fan a branch out sideways, so children go back to stacking. */
    .dhv-branch { flex-direction: column; gap: 1.5rem; }
    .dhv-branch > .dhv-children { flex-direction: column; }
    .dhv-board { padding: 1rem 0.9rem 1.25rem; }
}
