﻿/* ============================================================================
   Connecto application chrome -- shared navigation rail + top bar.
   Rendered by Controls/AppNav.ascx and included on every signed-in page.

   This is the SINGLE definition of the app's chrome. The dashboard used to
   carry its own copy inline, which meant the rail looked and behaved like a
   different product depending on which page you were on. Everything visual
   lives here now so every screen is the same by construction.

   EVERY class is prefixed `cnx-` on purpose: pages in this app define their
   own `.topbar` (27 of them), `.card`, `.btn` and `.logo`, and they keep their
   own stylesheets. The prefix guarantees this file can never collide.

   Layout contract for a host page:
       <body class="cnx-host">
   ...which clears the floating chrome. Nothing else is required.
   ========================================================================= */

/* Every chrome token lives on :root, NOT on .cnx-scope. Two things sit outside
   that element and still need them:

     * `body.cnx-host` and the host page's own rules are ABOVE it, which is why
       the content inset used to be spelled out as a bare `102px` in four
       separate places with the arithmetic repeated in a comment each time.

     * The collapsed rail's hover flyout is built by appnav.js and appended to
       <body>, so it is OUTSIDE .cnx-scope too. Scoped here, none of these
       resolved for it: `background: var(--cnx-hover)` on a flyout row was
       invalid at computed-value time and simply dropped, so hovering a
       sub-menu entry gave no highlight at all -- and the flyout's border,
       title and icons fell back to currentColor, i.e. black on near-black.

   The `cnx-` prefix is what makes :root safe here: no host page defines these
   names, so nothing can collide with them. */
:root {
    --cnx-pad: 16px;       /* frame inset around the floating chrome */
    --cnx-gap: 63px;       /* breathing space between rail and content */
    --cnx-rail: 64px;
    --cnx-rail-open: 216px;
    --cnx-top: 52px;
    --cnx-ico: 62px;

    /* Where the content column starts. THE alignment line of the app: the top
       bar's heading and every page's content both begin here, and both end at
       --cnx-pad from the right edge, so a screen reads as one column under one
       header rather than a header with a narrower slab floating beneath it. */
    --cnx-inset: calc(var(--cnx-pad) + var(--cnx-rail) + var(--cnx-gap));
    --cnx-inset-open: calc(var(--cnx-pad) + var(--cnx-rail-open) + var(--cnx-gap));

    --cnx-line: rgba(140, 170, 255, 0.12);
    --cnx-txt: #e9effd;
    --cnx-sub: #8ea3c7;
    --cnx-faint: #5b6f93;
    --cnx-acc: #00eac2;
    --cnx-acc2: #6fcaf8;
    --cnx-bad: #ff5c7a;
    --cnx-ok: #34e39d;
    --cnx-warn: #ffb454;
    --cnx-chip: rgba(111, 202, 248, 0.10);
    --cnx-hover: rgba(111, 202, 248, 0.08);
    --cnx-sheen: rgba(255, 255, 255, 0.06);
    --cnx-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.65);
}

/* Offsets so page content clears the floating chrome. !important because the
   host page's own body rule is usually written for a full viewport.

   All four sides are set here, not just the two the chrome physically covers.
   The right and bottom insets used to be left to each page to repeat, and the
   pages drifted: content stopped short of the top bar's right edge on some
   screens and ran to a different margin on others. Owning the whole frame here
   is what makes "aligned with the header, out to the power button" true by
   construction on every page, including ones not yet written. */
body.cnx-host {
    box-sizing: border-box !important;
    padding-left: var(--cnx-inset) !important;
    padding-right: var(--cnx-pad) !important;
    padding-top: 84px !important;     /* 16 + 52 + 16 */
    padding-bottom: var(--cnx-pad) !important;
    transition: padding-left 0.26s cubic-bezier(0.4, 0, 0.2, 1);
}

body.cnx-host.cnx-rail-open { padding-left: var(--cnx-inset-open) !important; }

/* Under Site.master everything sits inside one server form. Pages were built
   with their content as a direct child of <body>, so the form has to be
   transparent to layout or centred/flex pages would collapse to content width. */
body.cnx-host > form {
    width: 100%;
    min-width: 0;
    display: contents;
}

@media (prefers-reduced-motion: reduce) {
    body.cnx-host { transition: none; }
}

/* ------------------------------ RAIL ------------------------------ */
.cnx-rail {
    position: fixed;
    top: var(--cnx-pad);
    left: var(--cnx-pad);
    bottom: var(--cnx-pad);
    width: var(--cnx-rail);
    z-index: 9000;
    display: flex;
    flex-direction: column;
    font-family: 'Inter', 'Segoe UI', sans-serif;
    background: linear-gradient(180deg, rgba(12, 19, 35, 0.94), rgba(8, 13, 24, 0.96));
    border: 1px solid var(--cnx-line);
    border-radius: 20px;
    box-shadow: var(--cnx-shadow), inset 0 1px 0 var(--cnx-sheen);
    overflow: hidden;
    backdrop-filter: blur(22px) saturate(1.3);
    transition: width 0.26s cubic-bezier(0.4, 0, 0.2, 1);
}

    .cnx-rail.cnx-open {
        width: var(--cnx-rail-open);
        box-shadow: 0 30px 70px -20px rgba(0, 0, 0, 0.75), inset 0 1px 0 var(--cnx-sheen);
    }

@media (prefers-reduced-motion: reduce) {
    .cnx-rail { transition: none; }
}

.cnx-burger {
    display: flex;
    align-items: center;
    height: var(--cnx-top);
    flex-shrink: 0;
    cursor: pointer;
    color: #cfe2ff;
    border-bottom: 1px solid rgba(140, 170, 255, 0.08);
}

    .cnx-burger > i {
        flex: 0 0 var(--cnx-ico);
        width: var(--cnx-ico);
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 15px;
        color: var(--cnx-acc);
        transition: transform 0.26s cubic-bezier(0.4, 0, 0.2, 1);
    }

.cnx-rail.cnx-open .cnx-burger > i { transform: rotate(90deg); }

.cnx-brand {
    font-weight: 800;
    font-size: 14px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    background: linear-gradient(90deg, var(--cnx-acc), var(--cnx-acc2));
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

.cnx-menu {
    list-style: none;
    margin: 6px 0;
    padding: 0;
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
}

    .cnx-menu::-webkit-scrollbar { width: 5px; }
    .cnx-menu::-webkit-scrollbar-thumb { background: var(--cnx-line); border-radius: 5px; }
    .cnx-menu > li { position: relative; }

.cnx-link {
    position: relative;
    display: flex;
    align-items: center;
    height: 42px;
    color: #aac2e8;
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.18s, color 0.18s;
}

    .cnx-link > i:first-child {
        flex: 0 0 var(--cnx-ico);
        width: var(--cnx-ico);
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 15px;
        color: #6f9fd8;
        transition: color 0.18s;
    }

    .cnx-link::before {
        content: "";
        position: absolute;
        left: 0;
        top: 50%;
        width: 3px;
        height: 0;
        border-radius: 0 3px 3px 0;
        background: linear-gradient(180deg, var(--cnx-acc), var(--cnx-acc2));
        transform: translateY(-50%);
        transition: height 0.2s ease;
    }

    .cnx-link:hover { background: var(--cnx-hover); color: var(--cnx-acc); }
        .cnx-link:hover > i:first-child { color: var(--cnx-acc); }
        .cnx-link:hover::before { height: 20px; }

.cnx-menu > li.cnx-active > .cnx-link {
    color: var(--cnx-acc);
    background: linear-gradient(90deg, rgba(0, 234, 194, 0.16), rgba(111, 202, 248, 0.05) 55%, transparent);
}

    .cnx-menu > li.cnx-active > .cnx-link > i:first-child { color: var(--cnx-acc); }
    .cnx-menu > li.cnx-active > .cnx-link::before { height: 22px; }

/* ---- the group holding the current page ----
   Set when the page being viewed is a SUB-menu entry: thirteen of this app's
   screens live under one of the three collapsible groups, and until appnav.js
   learned to match them the rail said nothing at all on any of them.

   Deliberately weaker than cnx-active: no background wash and a shorter edge
   bar. "You are somewhere in here" and "this is the page" must not look alike,
   or the rail asserts you are on a screen you are not. */
.cnx-menu > li.cnx-active-parent > .cnx-link { color: #cfe2ff; }

    .cnx-menu > li.cnx-active-parent > .cnx-link > i:first-child { color: var(--cnx-acc2); }

    .cnx-menu > li.cnx-active-parent > .cnx-link::before {
        height: 12px;
        opacity: 0.55;
    }

/* The sub-entry that IS the current page. */
.cnx-sub a.cnx-on {
    color: var(--cnx-acc);
    background: linear-gradient(90deg, rgba(0, 234, 194, 0.12), transparent 85%);
    box-shadow: inset 2px 0 0 var(--cnx-acc);
}

    .cnx-sub a.cnx-on i { color: var(--cnx-acc); }

/* ---- keyboard focus ----
   Nothing in this chrome could take focus before: the menu entries are <a>
   elements with no href (the addresses live in data-go, because the URL-masking
   output filter rewrites href values), so they were neither tabbable nor
   announced. appnav.js gives them a role and a tabindex; this is what makes the
   focus visible once they have one.

   :focus-visible, not :focus -- a mouse click on a rail entry must not leave a
   ring behind on the way to another page.

   The offset is NEGATIVE on rail rows on purpose. The rail sets overflow:hidden
   for its rounded corners, so a ring drawn outside a full-width row is clipped
   along the left edge and reads as a broken box. */
.cnx-link:focus-visible,
.cnx-sub a:focus-visible,
.cnx-burger:focus-visible {
    outline: 2px solid var(--cnx-acc2);
    outline-offset: -2px;
    color: var(--cnx-acc);
}

    .cnx-link:focus-visible > i:first-child,
    .cnx-sub a:focus-visible i { color: var(--cnx-acc); }

/* Top-bar controls sit on the page rather than inside a clipped panel, so these
   take a normal outset ring. */
.cnx-top .cnx-btn:focus-visible,
.cnx-user:focus-visible {
    outline: 2px solid var(--cnx-acc2);
    outline-offset: 2px;
}

/* No focus rule for .cnx-flyout rows on purpose. Focusing a collapsed entry
   RAISES the flyout (appnav.js), but its rows are not tab stops -- the keyboard
   way into a group is ArrowRight, which opens the rail and focuses the real
   entry. The flyout is a hint here, not a menu to walk. */

.cnx-label {
    opacity: 0;
    white-space: nowrap;
    font-size: 12.5px;
    font-weight: 500;
    transition: opacity 0.16s ease;
    pointer-events: none;
}

.cnx-rail.cnx-open .cnx-label { opacity: 1; pointer-events: auto; }

.cnx-caret {
    margin-left: auto;
    margin-right: 16px;
    font-size: 9px;
    color: var(--cnx-faint);
    opacity: 0;
    transition: transform 0.22s ease, opacity 0.16s ease;
}

.cnx-rail.cnx-open .cnx-caret { opacity: 1; }
.cnx-menu > li.cnx-exp > .cnx-link .cnx-caret { transform: rotate(180deg); color: var(--cnx-acc); }

/* ---- group headers ----
   These name the three bands of the menu, and they used to be flat 9px capitals
   over an edge-to-edge hairline: legible, but decoration only.

   They carry information now. appnav.js marks the header of the band holding the
   current page with `cnx-sect-on` (see markActive), so the section you are in
   lights up -- which is the one thing a header here can say that the entries
   below it cannot. It survives collapsing: with the rail shut the label is gone
   and the RULE is what lights, so a 64px rail still shows which band you are in.

   Two states, one element:
     collapsed -- a short centred rule, standing in for the whole header
     open      -- an accent pip, the label, then a rule that fades out right */
.cnx-sect {
    display: flex;
    align-items: center;
    gap: 9px;
    height: 30px;
    margin-top: 9px;
    padding-left: 23px;
    font-size: 8.5px;
    font-weight: 800;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--cnx-faint);
    pointer-events: none;
    position: relative;
    transition: color 0.2s ease;
}

    /* The pip. Collapsed it is the header: 18px, centred in the icon column.
       Open it contracts to a 3px stub that reads as a bullet before the word. */
    .cnx-sect::before {
        content: "";
        position: absolute;
        left: 22px;
        top: 50%;
        width: 18px;
        height: 2px;
        border-radius: 2px;
        transform: translateY(-50%);
        background: linear-gradient(90deg, rgba(140, 170, 255, 0.34), rgba(140, 170, 255, 0.06));
        transition: width 0.24s cubic-bezier(0.4, 0, 0.2, 1), background 0.2s ease, box-shadow 0.2s ease;
    }

    /* The trailing rule, open only. A FLEX ITEM, not an absolute box: a
       pseudo-element on a flex container is laid out as its last child, so this
       starts wherever the label happens to end and needs no per-word offset --
       "CALLING" and "PRISONERS" both get a rule that meets the text exactly. */
    .cnx-sect::after {
        content: "";
        flex: 1;
        min-width: 0;
        height: 1px;
        margin-right: 16px;
        opacity: 0;
        background: linear-gradient(90deg, rgba(140, 170, 255, 0.20), transparent);
        transition: opacity 0.2s ease 0.06s;
    }

.cnx-rail.cnx-open .cnx-sect { padding-left: 34px; }
.cnx-rail.cnx-open .cnx-sect::before { width: 3px; }
.cnx-rail.cnx-open .cnx-sect::after { opacity: 1; }

/* The band holding the page being viewed. */
.cnx-sect.cnx-sect-on { color: var(--cnx-acc2); }

    .cnx-sect.cnx-sect-on::before {
        background: linear-gradient(90deg, var(--cnx-acc), var(--cnx-acc2));
        box-shadow: 0 0 10px -1px rgba(0, 234, 194, 0.75);
    }

    .cnx-sect.cnx-sect-on::after {
        background: linear-gradient(90deg, rgba(0, 234, 194, 0.34), transparent);
    }

@media (prefers-reduced-motion: reduce) {
    .cnx-sect, .cnx-sect::before, .cnx-sect::after { transition: none; }
}

.cnx-sub {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 0;
    overflow: hidden;
    background: rgba(0, 0, 0, 0.25);
    transition: max-height 0.22s ease;
}

.cnx-rail.cnx-open .cnx-menu > li.cnx-exp > .cnx-sub { max-height: 340px; }

    .cnx-sub a {
        display: flex;
        align-items: center;
        gap: 9px;
        height: 32px;
        padding: 0 16px 0 22px;
        font-size: 12px;
        color: #8fa8cf;
        text-decoration: none;
        cursor: pointer;
        white-space: nowrap;
        transition: color 0.15s, background 0.15s;
    }

        .cnx-sub a i {
            width: 18px;
            text-align: center;
            font-size: 11px;
            color: var(--cnx-faint);
            flex-shrink: 0;
        }

        .cnx-sub a:hover { color: var(--cnx-acc); background: var(--cnx-hover); }
            .cnx-sub a:hover i { color: var(--cnx-acc); }

.cnx-foot {
    flex-shrink: 0;
    height: 40px;
    display: flex;
    align-items: center;
    border-top: 1px solid rgba(140, 170, 255, 0.08);
    color: var(--cnx-faint);
    font-size: 9px;
    letter-spacing: 0.14em;
    white-space: nowrap;
    overflow: hidden;
}

    .cnx-foot > span:first-child {
        flex: 0 0 var(--cnx-ico);
        width: var(--cnx-ico);
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 12px;
        color: var(--cnx-acc);
    }

        /* The product mark. It carries its own colours, so it takes neither
           the accent tint nor the 55% fade the Font Awesome glyph here used to
           sit behind -- opacity on the span would apply to the image as a
           whole, and a brand mark at half strength reads as a rendering fault
           rather than as restraint.

           Sized explicitly: the file is square, and left to itself an <img>
           renders at its intrinsic pixel size, which has nothing to do with
           the 40px rail footer it has to sit inside. */
        .cnx-foot > span:first-child img {
            width: 19px;
            height: 19px;
            object-fit: contain;
            opacity: 0.92;
        }

/* Flyout shown when the rail is collapsed */
.cnx-flyout {
    position: fixed;
    z-index: 9500;
    min-width: 210px;
    background: #0e1626;
    border: 1px solid var(--cnx-line);
    border-radius: 12px;
    box-shadow: 0 18px 44px -12px rgba(0, 0, 0, 0.8);
    padding: 6px 0;
    font-family: 'Inter', 'Segoe UI', sans-serif;
    animation: cnxPop 0.12s ease;
}

@keyframes cnxPop { from { opacity: 0; transform: translateX(-4px); } to { opacity: 1; transform: none; } }

.cnx-flyout .cnx-fly-title {
    padding: 7px 14px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--cnx-sub);
}

/* Rows carry the SAME hover treatment as a top-level rail entry -- tinted
   background, accent text and icon, and the 3px accent bar down the leading
   edge. A sub-menu row is a destination exactly like a top-level one, so
   picking one has to feel identical; a fainter treatment here is what made it
   unclear which entry was actually about to be opened.

   The bar is why the panel no longer carries a permanent accent border on its
   left: two accent edges a few pixels apart read as decoration, and the point
   of this one is to mark the row under the cursor and nothing else. */
.cnx-flyout a {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 14px;
    font-size: 12.5px;
    color: var(--cnx-txt);
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.18s, color 0.18s;
}

    .cnx-flyout a::before {
        content: "";
        position: absolute;
        left: 0;
        top: 50%;
        width: 3px;
        height: 0;
        border-radius: 0 3px 3px 0;
        background: linear-gradient(180deg, var(--cnx-acc), var(--cnx-acc2));
        transform: translateY(-50%);
        transition: height 0.2s ease;
    }

    .cnx-flyout a i {
        width: 16px;
        text-align: center;
        font-size: 11px;
        color: var(--cnx-faint);
        transition: color 0.18s;
    }

    .cnx-flyout a:hover { background: var(--cnx-hover); color: var(--cnx-acc); }
        .cnx-flyout a:hover i { color: var(--cnx-acc); }
        .cnx-flyout a:hover::before { height: 20px; }

/* ------------------------------ TOP BAR ------------------------------ */
/* No panel and no border around the row: the bar is a set of free-standing
   controls floating over the page, each carrying its own glass surface. What
   sits behind the row is a blur that fades out downward, so content scrolled
   under the controls softens away instead of colliding with them -- and on a
   page that does not scroll, nothing of it shows at all. */
.cnx-top {
    position: fixed;
    top: var(--cnx-pad);
    left: var(--cnx-inset);
    right: var(--cnx-pad);
    height: var(--cnx-top);
    z-index: 8900;
    display: flex;
    align-items: center;
    gap: 10px;
    font-family: 'Inter', 'Segoe UI', sans-serif;
    color: var(--cnx-txt);
    transition: left 0.26s cubic-bezier(0.4, 0, 0.2, 1);
}

    /* z-index below the bar's own children, but still inside the stacking
       context the bar creates, so it covers page content and nothing else. */
    .cnx-top::before {
        content: "";
        position: absolute;
        inset: calc(var(--cnx-pad) * -1) calc(var(--cnx-pad) * -1) -14px;
        z-index: -1;
        pointer-events: none;
        -webkit-backdrop-filter: blur(14px);
        backdrop-filter: blur(14px);
        -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 64%, transparent 100%);
        mask-image: linear-gradient(180deg, #000 0%, #000 64%, transparent 100%);
    }

body.cnx-host.cnx-rail-open .cnx-top { left: var(--cnx-inset-open); }

/* Heading plus tagline, stacked, inside a bar that is 52px tall and stays that
   way -- the whole app's content offset is measured from --cnx-top, so growing
   the bar would move every page down. Two tight lines (18 + 2 + 13 = 33px)
   leave the same breathing room the single line had.

   min-width: 0 is load-bearing. A flex item will not shrink below its content
   by default, so without it a long tagline pushes the search box and the
   account controls off the right-hand edge instead of ellipsing. */
.cnx-page-head {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-width: 0;
    gap: 2px;
}

.cnx-page-title {
    font-size: 16px;
    font-weight: 800;
    line-height: 1.1;
    letter-spacing: -0.015em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-shadow: 0 1px 12px rgba(4, 8, 16, 0.65);
}

/* Quieter than the heading by weight and colour, not by size alone: at 11px a
   full-contrast line would compete with the title for the same glance. */
.cnx-page-sub {
    font-size: 11px;
    font-weight: 500;
    line-height: 1.1;
    letter-spacing: 0;
    color: var(--cnx-sub);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

    /* A page with no tagline gets no empty line: the heading re-centres in the
       bar exactly as it did before this existed. The markup writes the
       expression flush against its tags so this really does match. */
    .cnx-page-sub:empty { display: none; }

.cnx-spacer { flex: 1; }

/* Slot for the host page's own top-bar controls (see AppNav.ascx). */
.cnx-acts {
    display: flex;
    align-items: center;
    gap: 10px;
}

    /* Pages that contribute nothing must not leave a phantom flex gap. */
    .cnx-acts:empty { display: none; }

    /* Keeps the page's tools visually distinct from the account controls.
       Drawn only when the page actually contributed something. */
    .cnx-acts:not(:empty)::after {
        content: "";
        width: 1px;
        height: 20px;
        margin: 0 2px;
        background: var(--cnx-line);
    }

/* Global prisoner search */
.cnx-search {
    position: relative;
    flex: 1;
    max-width: 400px;
}

    .cnx-search > i {
        position: absolute;
        left: 12px;
        top: 50%;
        transform: translateY(-50%);
        /* --cnx-sub, not --cnx-faint: at 12px the faint tone sat one shade
           off the placeholder text and the icon read as absent. */
        color: var(--cnx-sub);
        font-size: 12px;
        pointer-events: none;
        transition: color 0.18s;
        /* The input's backdrop-filter makes it a stacking context, and the
           icon sits BEFORE it in the markup -- with z-index auto they share a
           paint layer where document order wins, so the input's translucent
           glass painted straight over the glyph. This is why the bar never
           visibly had its magnifier. */
        z-index: 1;
    }

    /* The icon joins the input's own focus treatment instead of staying
       inert beside a live control. */
    .cnx-search:focus-within > i { color: var(--cnx-acc); }

    /* Each control carries the glass the bar itself used to provide. */
    /* Right padding reserves the clear button's seat even while the button is
       hidden, so the text does not shift when it appears. */
    .cnx-search input {
        width: 100%;
        height: 36px;
        padding: 0 34px 0 34px;
        border-radius: 11px;
        border: 1px solid var(--cnx-line);
        background: rgba(15, 23, 42, 0.72);
        box-shadow: var(--cnx-shadow), inset 0 1px 0 var(--cnx-sheen);
        -webkit-backdrop-filter: blur(18px) saturate(1.25);
        backdrop-filter: blur(18px) saturate(1.25);
        color: var(--cnx-txt);
        font-family: inherit;
        font-size: 12.5px;
        outline: none;
        transition: border-color 0.2s, box-shadow 0.2s;
    }

        .cnx-search input::placeholder { color: var(--cnx-faint); }

        .cnx-search input:focus {
            border-color: rgba(0, 234, 194, 0.5);
            box-shadow: 0 0 0 3px rgba(0, 234, 194, 0.10), var(--cnx-shadow);
        }

    /* Clear button. display:none until the wrapper carries .cnx-has-text --
       an x beside an empty field promises something to undo that isn't there.
       appnav.js toggles the class on every input. */
    .cnx-search-clear {
        position: absolute;
        right: 6px;
        top: 50%;
        transform: translateY(-50%);
        /* Above the input's stacking context, same as the magnifier. Document
           order happens to put this after the input already, but that is an
           accident of markup, not something to lean on. */
        z-index: 1;
        width: 24px;
        height: 24px;
        display: none;
        align-items: center;
        justify-content: center;
        padding: 0;
        border: none;
        border-radius: 8px;
        background: transparent;
        color: var(--cnx-faint);
        font-size: 12px;
        cursor: pointer;
        transition: background 0.15s, color 0.15s;
    }

    .cnx-search.cnx-has-text .cnx-search-clear { display: flex; }

    .cnx-search-clear:hover {
        background: rgba(255, 92, 122, 0.12);
        color: var(--cnx-bad);
    }

    .cnx-search-clear:focus-visible {
        outline: none;
        background: rgba(255, 92, 122, 0.12);
        color: var(--cnx-bad);
        box-shadow: 0 0 0 2px rgba(255, 92, 122, 0.35);
    }

.cnx-results {
    position: absolute;
    top: 42px;
    left: 0;
    right: 0;
    display: none;
    background: #0e1626;
    border: 1px solid var(--cnx-line);
    border-radius: 12px;
    box-shadow: 0 24px 50px -14px rgba(0, 0, 0, 0.8);
    overflow: hidden;
    z-index: 9400;
}

    .cnx-results.cnx-on { display: block; animation: cnxPop 0.12s ease; }

.cnx-hit {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 14px;
    cursor: pointer;
    border-bottom: 1px solid var(--cnx-line);
}

    .cnx-hit:last-child { border-bottom: none; }
    .cnx-hit:hover { background: var(--cnx-hover); }

.cnx-hit-name { font-size: 12.5px; font-weight: 600; }
.cnx-hit-meta { font-size: 11px; color: var(--cnx-sub); }
.cnx-hit-right { margin-left: auto; text-align: right; }

.cnx-pill {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 20px;
    font-size: 10px;
    font-weight: 600;
}

.cnx-pill-ok { background: rgba(52, 227, 157, 0.14); color: var(--cnx-ok); }
.cnx-pill-bad { background: rgba(255, 92, 122, 0.14); color: var(--cnx-bad); }
.cnx-pill-warn { background: rgba(255, 180, 84, 0.16); color: var(--cnx-warn); }

.cnx-btn {
    position: relative;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 11px;
    border: 1px solid var(--cnx-line);
    background: rgba(15, 23, 42, 0.72);
    box-shadow: var(--cnx-shadow), inset 0 1px 0 var(--cnx-sheen);
    -webkit-backdrop-filter: blur(18px) saturate(1.25);
    backdrop-filter: blur(18px) saturate(1.25);
    color: var(--cnx-sub);
    cursor: pointer;
    font-size: 13px;
    text-decoration: none;
    flex-shrink: 0;
    transition: color 0.18s, border-color 0.18s;
}

    .cnx-btn:hover { color: var(--cnx-acc); border-color: rgba(0, 234, 194, 0.4); }

/* The jail this session is working in. Rendered for the platform owner only
   -- see ShowJailBadge in AppNav.ascx.vb -- because they are the one account
   that can be looking at any unit, and the money screens look identical
   whichever it is.

   Amber rather than the accent teal: this is a statement of context, not a
   control, and it should not read as something to press. It sits to the LEFT
   of the page's own buttons so it stays put as those come and go. */
.cnx-jail {
    display: flex;
    align-items: center;
    gap: 7px;
    height: 36px;
    padding: 0 12px;
    border-radius: 11px;
    border: 1px solid rgba(255, 180, 84, 0.32);
    background: rgba(255, 180, 84, 0.10);
    color: var(--cnx-warn, #ffb454);
    font-size: 11.5px;
    font-weight: 600;
    white-space: nowrap;
    flex-shrink: 0;
    cursor: default;
    -webkit-backdrop-filter: blur(18px) saturate(1.25);
    backdrop-filter: blur(18px) saturate(1.25);
}

    .cnx-jail i { font-size: 11px; opacity: 0.85; }

/* The booth chip. Same footprint as the jail badge beside it, but this one is
   a CONTROL -- it opens the picker -- so it takes the accent colour and a
   pointer, where the jail badge is amber and inert. */
.cnx-booth {
    display: flex;
    align-items: center;
    gap: 7px;
    height: 36px;
    padding: 0 12px;
    border-radius: 11px;
    border: 1px solid var(--cnx-line);
    background: rgba(15, 23, 42, 0.72);
    color: var(--cnx-sub);
    font-size: 11.5px;
    font-weight: 600;
    white-space: nowrap;
    flex-shrink: 0;
    cursor: pointer;
    -webkit-backdrop-filter: blur(18px) saturate(1.25);
    backdrop-filter: blur(18px) saturate(1.25);
    transition: color 0.18s, border-color 0.18s;
}

    .cnx-booth:hover { color: var(--cnx-acc); border-color: rgba(0, 234, 194, 0.4); }

    .cnx-booth:focus-visible {
        outline: none;
        box-shadow: 0 0 0 3px rgba(0, 234, 194, 0.22);
    }

    .cnx-booth i { font-size: 11px; opacity: 0.85; }

    @media (max-width: 1100px) {
        .cnx-booth span { display: none; }
        .cnx-booth { padding: 0 10px; }
    }

/* Wider than the sign-out dialog: that one asks a yes/no, this one lays out a
   room's worth of positions. */
.cnx-modal-wide { width: 520px; }

/* Tiles again, so changing booth mid-shift looks like the choice made at
   sign-in rather than a different feature. */
.cnx-booth-pick {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 10px;
    margin: 4px 0 18px;
    text-align: left;
}

.cnx-bp-msg {
    grid-column: 1 / -1;
    padding: 18px 4px;
    text-align: center;
    font-size: 12.5px;
    color: var(--cnx-sub);
}

.cnx-bp-wait { opacity: 0.5; }

.cnx-bp {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 5px;
    min-height: 96px;
    padding: 12px 10px;
    border-radius: 13px;
    border: 1px solid var(--cnx-line);
    background: rgba(8, 14, 26, 0.6);
    color: var(--cnx-txt);
    font: inherit;
    cursor: pointer;
    transition: border-color 0.18s, background 0.18s;
}

    .cnx-bp:hover:not(:disabled) {
        border-color: rgba(0, 234, 194, 0.5);
        background: rgba(0, 234, 194, 0.07);
    }

    .cnx-bp i { font-size: 15px; color: var(--cnx-acc2); }
    .cnx-bp .cnx-bp-n { font-size: 12.5px; font-weight: 600; line-height: 1.25; }

    .cnx-bp .cnx-bp-s {
        font-size: 9.5px;
        letter-spacing: 0.05em;
        text-transform: uppercase;
        color: var(--cnx-ok, #34e39d);
    }

    /* Whose booth it is, shown only when that is not this session's jail.
       Amber, matching the jail badge in the top bar, so "belongs to another
       unit" reads the same everywhere. */
    .cnx-bp .cnx-bp-j {
        font-size: 9.5px;
        color: var(--cnx-warn, #ffb454);
        line-height: 1.2;
        text-align: center;
    }

    /* Where you already are: settled, not selectable. */
    .cnx-bp.on {
        border-color: rgba(0, 234, 194, 0.55);
        background: rgba(0, 234, 194, 0.10);
        cursor: default;
    }

    .cnx-bp.busy { border-style: dashed; }
    .cnx-bp.busy i, .cnx-bp.busy .cnx-bp-s { color: var(--cnx-warn, #ffb454); }

    /* Narrow desks and tablets: keep the icon, drop the name rather than
       letting it push the account chip off the bar. The tooltip still says
       which jail, and the badge's presence still says "you chose one". */
    @media (max-width: 1100px) {
        .cnx-jail span { display: none; }
        .cnx-jail { padding: 0 10px; }
    }

.cnx-badge {
    position: absolute;
    top: -5px;
    right: -5px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 10px;
    background: var(--cnx-bad);
    color: #fff;
    font-size: 9.5px;
    font-weight: 700;
    display: none;
    align-items: center;
    justify-content: center;
}

/* A single pulse when the count goes UP, applied by appnav.js. Deliberately
   one shot rather than a loop: a badge that pulses forever is a thing people
   learn to stop seeing, and this has to still register on the twentieth
   message of the day. */
.cnx-badge.cnx-bump {
    animation: cnxBump 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Desktop alerts are not being allowed by this browser, on this exact origin.
   Amber, not red, and in the OPPOSITE corner to the unread badge: it is a
   standing condition of the machine, not a count, and the two must never be
   read as one thing. Silent failure is what let this sit unnoticed -- every
   notification path returns without a sound when permission is not granted,
   so the only honest place to say so is here, on every page. */
.cnx-btn.cnx-alerts-off::after {
    content: "\f071";                       /* fa-triangle-exclamation */
    font-family: "Font Awesome 6 Free", "Font Awesome 5 Free", sans-serif;
    font-weight: 900;
    position: absolute;
    left: -4px;
    bottom: -4px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--cnx-warn, #ffb454);
    color: #241300;
    font-size: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

@keyframes cnxBump {
    0% { transform: scale(1); }
    35% { transform: scale(1.45); box-shadow: 0 0 0 4px rgba(255, 92, 122, 0.25); }
    100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 92, 122, 0); }
}

@media (prefers-reduced-motion: reduce) {
    .cnx-badge.cnx-bump { animation: none; }
}

    .cnx-badge.cnx-on { display: flex; }

.cnx-user {
    display: flex;
    align-items: center;
    gap: 9px;
    height: 36px;
    padding: 0 11px 0 5px;
    border-radius: 11px;
    border: 1px solid var(--cnx-line);
    background: rgba(15, 23, 42, 0.72);
    box-shadow: var(--cnx-shadow), inset 0 1px 0 var(--cnx-sheen);
    -webkit-backdrop-filter: blur(18px) saturate(1.25);
    backdrop-filter: blur(18px) saturate(1.25);
    cursor: pointer;
    flex-shrink: 0;
    transition: border-color 0.18s;
}

    .cnx-user:hover { border-color: rgba(0, 234, 194, 0.4); }

    .cnx-user img {
        width: 27px;
        height: 27px;
        border-radius: 8px;
        object-fit: cover;
        border: 1px solid rgba(0, 234, 194, 0.5);
    }

    .cnx-user > span {
        font-size: 12px;
        font-weight: 600;
        max-width: 120px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

/* ------------------------------ TOOLTIP ------------------------------
   Themed bubble for the bar's controls, replacing the browser's native
   title balloon -- which ignores the product's theme and takes over a
   second to appear. A control opts in with data-tip="..."; anything inside
   .cnx-top still carrying title= is converted on boot by appnav.js, so a
   page button adopted into the bar gets the same bubble unedited.

   ONE element serves the whole app, appended to <body> exactly like the
   rail flyout and placed by appnav.js from getBoundingClientRect -- so no
   control needs a positioning context of its own, nothing can clip the
   bubble, and at the viewport's right edge it clamps instead of
   overflowing (the sign-out button). The rail's own data-tip entries
   belong to the flyout and are excluded in the script. */
.cnx-tip {
    position: fixed;
    z-index: 9700;   /* over the account panel (9600), under the modals */
    width: max-content;
    max-width: min(300px, calc(100vw - 24px));
    padding: 6px 11px;
    border-radius: 9px;
    background: #131d31;
    border: 1px solid var(--cnx-line);
    box-shadow: 0 14px 34px -12px rgba(0, 0, 0, 0.8), inset 0 1px 0 var(--cnx-sheen);
    color: var(--cnx-txt);
    font-family: 'Inter', 'Segoe UI', sans-serif;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.01em;
    line-height: 1.4;
    pointer-events: none;   /* must never steal the hover it is describing */
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
}

    /* Where the bubble slides in FROM says where it points: down from a bar
       control, rightward off the burger. */
    .cnx-tip-b { transform: translateY(5px); }
    .cnx-tip-r { transform: translateX(-5px); }

    .cnx-tip.cnx-on {
        opacity: 1;
        visibility: visible;
        transform: none;
    }

    /* Ties the bubble to the control it names -- the same diamond the
       account panel aims at its chip. --cnx-tip-ax is set by appnav.js:
       when the bubble has been clamped at a viewport edge, the arrow stays
       centred under the CONTROL rather than the bubble. */
    .cnx-tip::before {
        content: "";
        position: absolute;
        width: 9px;
        height: 9px;
        background: #131d31;
        border-left: 1px solid var(--cnx-line);
        transform: rotate(45deg);
    }

    .cnx-tip-b::before {
        top: -5px;
        left: var(--cnx-tip-ax, 50%);
        margin-left: -4.5px;
        border-top: 1px solid var(--cnx-line);
        border-radius: 2px 0 0 0;
    }

    .cnx-tip-r::before {
        left: -5px;
        top: 50%;
        margin-top: -4.5px;
        border-bottom: 1px solid var(--cnx-line);
        border-radius: 0 0 0 2px;
    }

/* The pause before it shows lives in the script; with motion reduced the
   bubble simply appears and disappears there. */
@media (prefers-reduced-motion: reduce) {
    .cnx-tip { transition: none; }
}

/* --------------------------- ACCOUNT PANEL ---------------------------
   Three bands -- identity, identifiers, actions -- separated by hairlines
   that run the full width. The panel has no padding of its own for that
   reason: a rule that stops short of the edges reads as a stray underline
   rather than as a division, so each band carries its own inset instead. */
.cnx-drop {
    position: fixed;
    top: 78px;
    right: 16px;
    /* 296px, not 260px: a Cloud Agent ID is 13 characters of monospace and
       was wrapping under its own label. */
    width: 296px;
    z-index: 9600;
    display: none;
    padding: 0;
    overflow: hidden;
    border-radius: 16px;
    background: #0e1626;
    border: 1px solid var(--cnx-line);
    /* Two shadows: the near one seats the panel against the bar, the far one
       lifts it off the page. A single large blur does one or the other. */
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4), 0 28px 60px -18px rgba(0, 0, 0, 0.85);
    font-family: 'Inter', 'Segoe UI', sans-serif;
    color: var(--cnx-txt);
}

    .cnx-drop.cnx-on {
        display: block;
        /* Grows from its own top-right corner, where the chip that opened it
           is, rather than sliding in from the left like the rail flyouts. */
        transform-origin: 100% 0;
        animation: cnxDropIn 0.16s cubic-bezier(0.16, 1, 0.3, 1);
    }

@keyframes cnxDropIn {
    from { opacity: 0; transform: translateY(-6px) scale(0.97); }
    to   { opacity: 1; transform: none; }
}

/* Ties the panel to the chip that opened it. Dropped on the narrowest screens,
   where the panel spans the width and has no single point to aim at. */
.cnx-drop::before {
    content: "";
    position: absolute;
    top: -5px;
    right: 26px;
    width: 9px;
    height: 9px;
    background: #131d31;
    border-left: 1px solid var(--cnx-line);
    border-top: 1px solid var(--cnx-line);
    transform: rotate(45deg);
    border-radius: 2px 0 0 0;
}

/* ---- band 1: who ---- */
.cnx-drop-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 16px 16px 14px;
    /* Lit from the top-left so the band lifts away from the flat body below
       without needing a border to say where it ends. */
    background:
        radial-gradient(120% 140% at 0% 0%, rgba(0, 234, 194, 0.10), transparent 60%),
        linear-gradient(180deg, rgba(111, 202, 248, 0.06), transparent);
    border-bottom: 1px solid var(--cnx-line);
}

    .cnx-drop-head img {
        width: 46px;
        height: 46px;
        border-radius: 13px;
        object-fit: cover;
        flex-shrink: 0;
        border: 1px solid rgba(0, 234, 194, 0.45);
        /* Squircle, not a circle: it echoes the 27px chip in the bar that
           opens this, so the panel reads as that control enlarged. */
        box-shadow: 0 0 0 3px rgba(0, 234, 194, 0.08);
    }

.cnx-drop-who { min-width: 0; }

    .cnx-drop-who h4 {
        margin: 0;
        font-size: 14.5px;
        font-weight: 650;
        letter-spacing: -0.01em;
        line-height: 1.25;
        /* A long name truncates rather than pushing the panel wider or
           wrapping under the avatar. */
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

.cnx-drop-role {
    display: inline-block;
    margin-top: 5px;
    max-width: 100%;
    padding: 2.5px 8px;
    border-radius: 999px;
    background: var(--cnx-chip);
    border: 1px solid rgba(111, 202, 248, 0.22);
    color: var(--cnx-acc2);
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---- band 2: identifiers ---- */
.cnx-drop-facts {
    margin: 0;
    padding: 6px 16px 10px;
}

    .cnx-drop-facts > div {
        display: grid;
        /* Two columns rather than space-between: the values then start on one
           line down the panel instead of ragging to wherever each label ends. */
        grid-template-columns: auto 1fr;
        gap: 10px;
        align-items: baseline;
        padding: 6px 0;
        border-bottom: 1px solid rgba(140, 170, 255, 0.06);
    }

        .cnx-drop-facts > div:last-child { border-bottom: 0; }

    .cnx-drop-facts dt {
        font-size: 10px;
        font-weight: 600;
        letter-spacing: 0.05em;
        text-transform: uppercase;
        color: var(--cnx-faint);
        white-space: nowrap;
    }

    .cnx-drop-facts dd {
        margin: 0;
        text-align: right;
        /* Monospace because these are identifiers, not words: it makes them
           scannable and comparable against what the DB or TeleCMI shows. */
        font-family: 'JetBrains Mono', ui-monospace, 'Consolas', monospace;
        font-size: 11.5px;
        font-weight: 600;
        color: var(--cnx-txt);
        overflow-wrap: anywhere;
    }

/* ---- band 3: actions ---- */
.cnx-drop-foot {
    padding: 10px 12px 12px;
    border-top: 1px solid var(--cnx-line);
    background: rgba(0, 0, 0, 0.18);
}

.cnx-logout {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    padding: 9px 0;
    border-radius: 10px;
    /* Quiet until wanted. A permanently outlined red button in a panel opened
       just to read an ID makes signing out look like the purpose of the
       panel; the intent only shows on approach. */
    background: transparent;
    border: 1px solid transparent;
    color: var(--cnx-sub);
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.16s, color 0.16s, border-color 0.16s;
}

    .cnx-logout i { font-size: 12px; }

    .cnx-logout:hover {
        background: rgba(255, 92, 122, 0.10);
        border-color: rgba(255, 92, 122, 0.45);
        color: var(--cnx-bad);
    }

    .cnx-logout:focus-visible {
        outline: none;
        background: rgba(255, 92, 122, 0.10);
        border-color: var(--cnx-bad);
        color: var(--cnx-bad);
    }

    .cnx-logout:active { transform: translateY(1px); }

/* ------------------------------ MODAL ------------------------------ */
.cnx-veil {
    position: fixed;
    inset: 0;
    z-index: 9900;
    display: none;
    align-items: center;
    justify-content: center;
    background: rgba(4, 8, 16, 0.68);
    backdrop-filter: blur(4px);
    font-family: 'Inter', 'Segoe UI', sans-serif;
}

    .cnx-veil.cnx-on { display: flex; }

.cnx-modal {
    width: 390px;
    max-width: 92vw;
    padding: 26px 26px 20px;
    border-radius: 18px;
    background: #0e1626;
    border: 1px solid var(--cnx-line);
    box-shadow: 0 40px 80px -24px rgba(0, 0, 0, 0.85);
    text-align: center;
    color: var(--cnx-txt);
    animation: cnxBox 0.18s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}

@keyframes cnxBox { from { opacity: 0; transform: translateY(10px) scale(0.97); } to { opacity: 1; transform: none; } }

.cnx-modal-ico {
    width: 52px;
    height: 52px;
    margin: 0 auto 14px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 20px;
    color: var(--cnx-bad);
    background: rgba(255, 92, 122, 0.12);
    border: 1px solid rgba(255, 92, 122, 0.3);
}

.cnx-modal h3 { margin: 0 0 8px; font-size: 17px; }
.cnx-modal p { margin: 0 0 20px; font-size: 12.5px; line-height: 1.55; color: var(--cnx-sub); }

.cnx-actions { display: flex; gap: 10px; }

.cnx-act {
    flex: 1;
    height: 40px;
    border-radius: 11px;
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.18s;
}

.cnx-ghost { background: transparent; border: 1px solid var(--cnx-line); color: var(--cnx-sub); }
    .cnx-ghost:hover { color: var(--cnx-txt); border-color: var(--cnx-sub); }

.cnx-danger { background: var(--cnx-bad); border: 1px solid var(--cnx-bad); color: #fff; }
    .cnx-danger:hover { filter: brightness(1.1); }
    .cnx-danger:disabled { opacity: 0.7; cursor: default; }

/* --------------------- PAGE TRANSITION OVERLAY --------------------- */
/* Shown for the gap between asking for a page and getting it. Every screen in
   the app gets this from the chrome, so a navigation never looks like a click
   that did nothing. Above the sign-out modal (9900): logging out is itself a
   navigation, and the veil should cover it too. */
.cnx-nav-veil {
    position: fixed;
    inset: 0;
    z-index: 9950;
    display: none;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 18px;
    background: rgba(6, 10, 19, 0.72);
    -webkit-backdrop-filter: blur(7px) saturate(1.1);
    backdrop-filter: blur(7px) saturate(1.1);
    font-family: 'Inter', 'Segoe UI', sans-serif;
}

    .cnx-nav-veil.cnx-on {
        display: flex;
        animation: cnxVeilIn 0.16s ease;
    }

@keyframes cnxVeilIn { from { opacity: 0; } to { opacity: 1; } }

/* One ring, one animation. This was four animated layers -- a bloom that
   breathed, an arc with a glowing comet head riding its leading edge, a
   counter-rotating inner arc, and a pulsing core -- plus a light sweeping
   across the label: six animations running at once, in a 110px halo, to say
   "the next page is coming". It read as a special effect rather than as a
   product, and a page transition is the most routine thing this app does.

   What is left is a 32px track with a single tapered arc turning at a constant
   rate. The taper is the only ornament: it is what keeps it from reading as
   the browser's own border-spinner. */
.cnx-nav-load {
    position: relative;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    border: 2px solid var(--cnx-line);
}

    /* The arc sits ON the track, not inside it, so both read as one stroke.
       A pseudo-element rather than a child: the markup is duplicated in
       AppNav.ascx and login.aspx, so every element saved is saved twice. */
    .cnx-nav-load::after {
        content: "";
        position: absolute;
        inset: -2px;
        border-radius: 50%;
        background: conic-gradient(from 0deg,
            transparent 0deg,
            transparent 200deg,
            var(--cnx-acc2) 330deg,
            var(--cnx-acc) 360deg);
        /* Punches out the middle, leaving a 2px band. Both spellings: the
           unprefixed property is still not universal in the Chromium builds
           these terminals run. */
        -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
        mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
        animation: cnxSpin 0.8s linear infinite;
    }

@keyframes cnxSpin { to { transform: rotate(360deg); } }

/* Sentence case at a readable size, in the same muted grey as every other
   secondary label in the product. It was 10px uppercase at 0.42em tracking
   with a gradient sweeping through the glyphs -- which is a title treatment,
   not a status line, and the text here is set at runtime ("Loading",
   "Signing in", "Connecting to the phone network"). */
.cnx-nav-text {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--cnx-sub);
}

/* Slower, not stopped. A frozen ring reads as a hung page, which is the one
   thing an overlay like this must never say. */
@media (prefers-reduced-motion: reduce) {
    .cnx-nav-load::after { animation-duration: 2.4s; }
}

/* A permission check is in flight for a menu entry. Without this a gated
   click looked identical to a click that did nothing at all. */
body.cnx-navigating .cnx-rail {
    cursor: progress;
}

    body.cnx-navigating .cnx-menu {
        opacity: 0.55;
        pointer-events: none;
        transition: opacity 0.15s ease;
    }

/* Narrow screens: drop the search, keep everything else usable. The tagline
   goes with it -- it is the one thing in the bar that explains rather than
   does, so it is what should yield when the heading and the page's own
   controls are competing for the same width. */
@media (max-width: 900px) {
    .cnx-search { display: none; }
    .cnx-page-sub { display: none; }
}

/* Below this a fixed 296px panel plus its inset crowds the viewport edge, so
   it spans the width instead -- and with nothing left to aim at, the caret goes. */
@media (max-width: 380px) {
    .cnx-drop {
        left: 12px;
        right: 12px;
        width: auto;
    }

        .cnx-drop::before { display: none; }
}

/* The panel grows from the chip; with motion reduced it simply appears. */
@media (prefers-reduced-motion: reduce) {
    .cnx-drop.cnx-on { animation: none; }
}
