/* mftcore panel — design layer on top of Bootstrap 5.
   Dark-first: this is a NOC tool, usually on a wall display or a second
   monitor, so contrast and scan-ability beat decoration. */

:root {
    --mft-sidebar: 232px;
    --mft-accent: #4c8dff;
    --mft-surface: rgba(255, 255, 255, 0.028);
    --mft-border: rgba(255, 255, 255, 0.09);
    --mft-radius: 10px;
    /* Solid colour for sticky table headers. Must be opaque -- see the rule on
       thead th. Picked to sit just darker than the card surface. */
    --mft-head-bg: #1b1f24;
}

body {
    font-size: 0.9375rem;
    background:
        radial-gradient(900px 420px at 80% -8%, rgba(76, 141, 255, 0.07), transparent 62%),
        var(--bs-body-bg);
    min-height: 100vh;
}

/* ------------------------------------------------------------- utilities
   The CSP is `style-src 'self'` with no 'unsafe-inline', so inline style=""
   attributes are dropped by the browser. Anything that would have been an
   inline style has to live here as a class. */

/* Icon sprite holder: defines <symbol>s, renders nothing. Without this it
   lays out as a visible empty box and pushes the whole page down. */
.sprite {
    display: none;
}

/* Default size for every sprite icon. Without this a bare <svg class="ico">
   -- one not covered by a more specific rule, like the Tools button -- falls
   back to the SVG replaced-element default and renders enormous. The scoped
   rules below are more specific, so they still win where they apply. */
.ico {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
}

.w-filter   { max-width: 18rem; }
.w-op       { max-width: 6.5rem; }
.trunc      { max-width: 16rem; }
.trunc-wide { max-width: 28rem; }

/* ---------------------------------------------------------------- shell */

.mft-shell {
    display: flex;
    min-height: 100vh;
}

.mft-side {
    width: var(--mft-sidebar);
    flex: 0 0 var(--mft-sidebar);
    border-right: 1px solid var(--mft-border);
    background: rgba(0, 0, 0, 0.16);
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
}

.mft-main {
    flex: 1 1 auto;
    min-width: 0;           /* lets wide tables scroll instead of stretching */
    display: flex;
    flex-direction: column;
}

.mft-brand {
    display: block;
    padding: 1.15rem 1.15rem 1rem;
    border-bottom: 1px solid var(--mft-border);
    color: var(--bs-body-color);
    text-decoration: none;
}

.mft-brand:hover {
    color: var(--bs-body-color);
    background: rgba(255, 255, 255, 0.03);
}

.mft-brand-name {
    font-size: 1.1rem;
    font-weight: 650;
    letter-spacing: -0.015em;
    line-height: 1.1;
}

.mft-brand-sub {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--bs-secondary-color);
    margin-top: 0.2rem;
}

.mft-nav {
    padding: 0.7rem 0.6rem;
    overflow-y: auto;
    flex: 1 1 auto;
}

.mft-nav-label {
    font-size: 0.66rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--bs-secondary-color);
    padding: 0.85rem 0.55rem 0.35rem;
}

.mft-nav a {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0.55rem;
    border-radius: 7px;
    color: var(--bs-body-color);
    text-decoration: none;
    font-size: 0.875rem;
    line-height: 1.2;
}

.mft-nav a:hover {
    background: rgba(255, 255, 255, 0.055);
}

.mft-nav a.active {
    background: rgba(76, 141, 255, 0.15);
    color: #cfe0ff;
    font-weight: 600;
    box-shadow: inset 2px 0 0 var(--mft-accent);
}

.mft-nav .ico {
    width: 16px;
    height: 16px;
    flex: 0 0 16px;
    opacity: 0.8;
}

/* Signed-in user + sign out, top right of the header bar. Separated from the
   page actions by a rule so "Sign out" is never mistaken for a page action. */
.mft-topbar-user {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding-left: 0.85rem;
    border-left: 1px solid var(--mft-border);
}

.mft-user-chip {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    text-decoration: none;
    color: var(--bs-body-color);
    max-width: 14rem;
}

.mft-user-chip:hover {
    color: var(--mft-accent);
}

.mft-user-chip .ico {
    width: 15px;
    height: 15px;
    opacity: 0.7;
    flex: 0 0 15px;
}

.mft-user-name {
    font-weight: 550;
    font-size: 0.85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------------------------------------------------------- page header */

.mft-topbar {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 1.4rem;
    border-bottom: 1px solid var(--mft-border);
    background: rgba(0, 0, 0, 0.12);
    position: sticky;
    top: 0;
    z-index: 20;
    backdrop-filter: blur(7px);
}

.mft-page-title {
    font-size: 1.08rem;
    font-weight: 640;
    margin: 0;
    letter-spacing: -0.01em;
}

.mft-page-sub {
    font-size: 0.78rem;
    color: var(--bs-secondary-color);
    margin: 0.1rem 0 0;
}

.mft-body {
    --mft-body-pad: 1.4rem;
    padding: var(--mft-body-pad);
    flex: 1 1 auto;
}

/* --------------------------------------------------------------- cards */

.card {
    border-color: var(--mft-border);
    border-radius: var(--mft-radius);
    background: var(--mft-surface);
}

.card-header {
    background: rgba(255, 255, 255, 0.022);
    border-bottom-color: var(--mft-border);
    font-size: 0.85rem;
    padding: 0.7rem 1rem;
}

/* Stat tiles on the dashboard. */
.stat {
    padding: 0.95rem 1.05rem;
}

.stat-label {
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--bs-secondary-color);
}

.stat-value {
    font-size: 1.45rem;
    font-weight: 640;
    line-height: 1.25;
    margin-top: 0.25rem;
    letter-spacing: -0.02em;
}

/* For values that are words rather than numbers. */
.stat-value.is-text {
    font-size: 1.15rem;
}

.stat-note {
    font-size: 0.76rem;
    color: var(--bs-secondary-color);
    margin-top: 0.15rem;
}

/* ------------------------------------------------------- dashboard hero */

.dash-hero {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    text-align: center;
    /* Sit below the header with breathing room; no longer forced to fill the
       viewport, so the router photo underneath is always visible. */
    min-height: auto;
    padding: clamp(2rem, 8vh, 6rem) 1rem 3rem;
}

.dash-hero-name {
    /* The vw term does most of the work on a phone, so the floor has to be
       large too or a narrow screen collapses straight to it and looks small. */
    font-size: clamp(2.4rem, 7.5vw, 4.6rem);
    font-weight: 700;
    letter-spacing: -0.035em;
    line-height: 1.1;
    background: linear-gradient(180deg, #ffffff 0%, #9fb6d6 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    text-wrap: balance;
}

.dash-hero-sub {
    margin-top: 0.85rem;
    font-size: clamp(1rem, 2.4vw, 1.6rem);
    font-weight: 500;
    /* Wide tracking reads as a strapline rather than a second heading. */
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--bs-secondary-color);
}

/* Front-panel photo of the device this panel manages.
   The stage is deliberately taller than the viewport: that extra height is the
   scroll distance app.js maps onto the zoom. The figure sticks inside it so the
   photo stays in view the whole time it is scaling. */
.dash-router-stage {
    position: relative;
    height: 150vh;
    /* Full-bleed: cancel .mft-body's padding so the photo spans the whole
       content column. The breakout lives HERE rather than on the figure --
       overflow-x: clip below clips to this element's own box, so a figure
       trying to escape it would simply be cut back to the padded width. */
    margin-inline: calc(-1 * var(--mft-body-pad));
    /* Belt and braces: the frame above already clips the zoom, this stops a
       full-bleed child from ever widening the page. */
    overflow-x: clip;
}

.dash-router {
    position: sticky;
    top: 14vh;
    /* Fills the (already full-bleed) stage. */
    margin: 0;
    max-width: none;
    text-align: center;
}

/* The window the photo zooms *inside*. Height comes from the image's natural
   size (via the plate below), NOT an aspect-ratio + percentage-height chain --
   that chain collapsed to height:0 in the browser, which scattered the LED
   overlay. overflow:clip still hides the zoomed-up overflow, because a CSS
   transform does not change the layout box the frame reserves. */
.dash-router-frame {
    position: relative;
    overflow: clip;
    display: block;
}

/* Photo + LED overlay move as one. The scroll-zoom scales THIS wrapper (not the
   bare <img>), so the LEDs stay pinned to their ports at every zoom level. Its
   height is the image's height, so the LED layer (inset:0) maps exactly onto
   the photo. */
.dash-router-plate {
    display: block;   /* it is a <span>; without this it stays inline and the
                         absolutely-positioned LED layer gets a broken box */
    position: relative;
    width: 100%;
    /* The plate carries the aspect-ratio itself, so the box has a definite
       height from its own definite width -- correct even before the image has
       loaded, and not dependent on a percentage-height chain (which collapsed
       to 0 and scattered the LEDs). The LED layer maps onto this box. */
    aspect-ratio: 2205 / 283;
    transform: scale(1);
    transform-origin: center center;
    will-change: transform;
    line-height: 0;
}

.dash-router img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: fill;   /* box ratio == image ratio, so no distortion, no crop */
    /* Lift it off the flat background so the panel photo reads as an object. */
    filter: drop-shadow(0 14px 30px rgba(0, 0, 0, 0.5));
}

/* ---- port status LEDs -------------------------------------------------
   Positions are fixed points on the NE05E-SQ photo, so they live as classes
   (the CSP forbids inline style). Size is a PERCENT of the plate width, so a
   round dot scales with the image and the zoom automatically. Only the up/down
   class is dynamic, set per port by the dashboard from live oper-status. */
.led-layer {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.led {
    position: absolute;
    width: 1.7%;
    aspect-ratio: 1;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    /* down/idle: a dark, unlit socket -- visible on the light RJ45 area but
       never mistaken for activity. */
    background: rgba(10, 20, 15, 0.38);
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35);
}

/* Active link: a bright green LED with a glow, blinking like real port
   activity. Green core with a near-white hot centre so it pops on both the
   dark SFP cages and the light RJ45 jacks. */
/* A lit port reads as a real LED: white-hot core, saturated green body, and a
   double halo so it carries against both the dark SFP cages and the pale RJ45
   plastic. */
.led-up {
    background: radial-gradient(circle at 50% 42%, #ffffff 0%, #b6ffd4 22%,
                                #2bff88 48%, #0ad161 100%);
    box-shadow: 0 0 6px 2px rgba(43, 255, 136, 1),
                0 0 16px 6px rgba(34, 230, 106, 0.75),
                0 0 30px 12px rgba(34, 230, 106, 0.35);
    animation: led-blink 1.4s ease-in-out infinite;
}

/* A few phase offsets so the lit ports don't pulse in lockstep -- assigned by
   port number modulo 4 in the markup (no inline style needed). */
.led-b { animation-delay: 0.32s; }
.led-c { animation-delay: 0.64s; }
.led-d { animation-delay: 0.96s; }

@keyframes led-blink {
    0%, 100% { opacity: 1;    filter: brightness(1.15); }
    50%      { opacity: 0.72; filter: brightness(0.85); }
}

.dash-router figcaption {
    margin-top: 1.1rem;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--bs-secondary-color);
}

@media (max-width: 991.98px) {
    .dash-router-stage { height: 128vh; }
}

/* Someone who asked the OS to reduce motion gets no scroll-zoom (that is
   decorative motion). The port LEDs still blink: they are a requested status
   indicator, not decoration -- a link light that does not blink conveys less. */
@media (prefers-reduced-motion: reduce) {
    .dash-router-stage { height: auto; padding: 2.4rem 0; }
    .dash-router { position: static; top: auto; }
    .dash-router-plate { transform: none !important; }
    .dash-router-frame { overflow: visible; }
}

/* LED port positions on the NE05E-SQ photo (fixed; generated, do not hand-edit) */
.led-p0{left:42.79%;top:53.7%}
.led-p1{left:42.79%;top:76.0%}
.led-p2{left:45.76%;top:53.7%}
.led-p3{left:45.76%;top:76.0%}
.led-p4{left:49.52%;top:53.7%}
.led-p5{left:49.52%;top:76.0%}
.led-p6{left:52.47%;top:53.7%}
.led-p7{left:52.47%;top:76.0%}
.led-p8{left:55.37%;top:53.7%}
.led-p9{left:55.37%;top:76.0%}
.led-p10{left:58.32%;top:53.7%}
.led-p11{left:58.32%;top:76.0%}
.led-p12{left:62.52%;top:53.7%}
.led-p13{left:62.52%;top:76.7%}
.led-p14{left:65.46%;top:53.7%}
.led-p15{left:65.46%;top:76.7%}
.led-p16{left:68.46%;top:53.7%}
.led-p17{left:68.46%;top:76.7%}
.led-p18{left:71.41%;top:53.7%}
.led-p19{left:71.41%;top:76.7%}
.led-p20{left:75.83%;top:54.4%}
.led-p21{left:75.83%;top:74.6%}
.led-p22{left:78.71%;top:54.4%}
.led-p23{left:78.71%;top:74.6%}
.led-p24{left:81.59%;top:54.4%}
.led-p25{left:81.59%;top:74.6%}
.led-p26{left:84.40%;top:54.4%}
.led-p27{left:84.40%;top:74.6%}

@media (max-width: 991.98px) {
    /* The sidebar becomes a horizontal nav strip on small screens and eats a
       lot of the viewport, so centring against 100vh would push the text off
       the bottom. Centre against what is actually left instead. */
    .dash-hero-sub { letter-spacing: 0.11em; }
}

/* --------------------------------------------------------------- tables */

.table {
    margin-bottom: 0;
    --bs-table-border-color: var(--mft-border);
}

.table > thead > tr > th {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.075em;
    font-weight: 600;
    color: var(--bs-secondary-color);
    border-bottom-width: 1px;
    white-space: nowrap;
    padding: 0.6rem 0.85rem;
    /* Opaque, not a translucent overlay: this header is sticky, so rows scroll
       underneath it. Any alpha here lets the row text read straight through the
       column names. */
    background: var(--mft-head-bg);
    position: sticky;
    top: 0;
    z-index: 3;
}

.table > tbody > tr > td {
    padding: 0.55rem 0.85rem;
    vertical-align: middle;
}

/* Alternating row colour, everywhere. Long tables here are wide and dense
   (35 interfaces, 707 config lines, 36 ACL rules) and banding is what keeps
   the eye on one row across the full width.
   Set on the cells rather than the row: Bootstrap paints its own background on
   > * , so a rule on <tr> alone is overpainted and never shows. */
.table > tbody > tr:nth-of-type(even) > * {
    background-color: rgba(255, 255, 255, 0.028);
}

/* Hover and selection must out-rank the banding, or half the rows stop
   responding to the pointer. */
.table-hover > tbody > tr:hover > * {
    background-color: rgba(255, 255, 255, 0.065);
}

.table > tbody > tr.table-active > * {
    background-color: rgba(76, 141, 255, 0.16);
}

/* Bootstrap collapses these to display:none; without a rule the row would
   render as a stray block once shown. Kept for any inline expander. */
tr.collapse:not(.show) {
    display: none;
}

tr.collapse.show {
    display: table-row;
}

/* Alternating rows everywhere, without needing .table-striped on each table.
   Driven through Bootstrap's own --bs-table-bg-type so hover and .table-active
   still win: those use --bs-table-bg-state, which sits above type in the
   cascade. Setting a plain background here would beat both and kill the hover. */
.table > tbody > tr:nth-of-type(odd) > * {
    --bs-table-color-type: var(--bs-table-striped-color);
    --bs-table-bg-type: var(--bs-table-striped-bg);
}

.table {
    --bs-table-striped-bg: rgba(255, 255, 255, 0.03);
    --bs-table-striped-color: var(--bs-body-color);
}

.table-scroll {
    max-height: 70vh;
    overflow: auto;
    border-radius: var(--mft-radius);
}

.mono {
    font-family: var(--bs-font-monospace);
    font-size: 0.83em;
}

/* Status pill with a leading dot — reads faster than a coloured word. */
.pill {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.75rem;
    font-weight: 550;
    padding: 0.16rem 0.55rem;
    border-radius: 999px;
    border: 1px solid transparent;
    white-space: nowrap;
}

.pill::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    flex: 0 0 6px;
}

.pill-up      { color: #6ee7a8; background: rgba(25, 135, 84, 0.14); border-color: rgba(110, 231, 168, 0.25); }
.pill-down    { color: #ff8b93; background: rgba(220, 53, 69, 0.14); border-color: rgba(255, 139, 147, 0.25); }
.pill-muted   { color: #9aa4b2; background: rgba(154, 164, 178, 0.12); border-color: rgba(154, 164, 178, 0.22); }
.pill-warn    { color: #ffcf7a; background: rgba(255, 193, 7, 0.13); border-color: rgba(255, 207, 122, 0.25); }
.pill-info    { color: #8fc0ff; background: rgba(76, 141, 255, 0.14); border-color: rgba(143, 192, 255, 0.25); }

/* Empty states: never leave a bare blank table. */
.empty {
    text-align: center;
    padding: 2.4rem 1rem;
    color: var(--bs-secondary-color);
}

.empty .ico {
    width: 26px;
    height: 26px;
    opacity: 0.35;
    margin-bottom: 0.55rem;
}

.empty-title {
    font-weight: 560;
    color: var(--bs-body-color);
    margin-bottom: 0.2rem;
}

/* --------------------------------------------------------------- forms */

.form-label {
    font-size: 0.78rem;
    font-weight: 550;
    margin-bottom: 0.28rem;
    color: var(--bs-secondary-color);
}

.form-control, .form-select {
    background-color: rgba(0, 0, 0, 0.22);
    border-color: var(--mft-border);
}

.form-control:focus, .form-select:focus {
    background-color: rgba(0, 0, 0, 0.3);
    border-color: var(--mft-accent);
    box-shadow: 0 0 0 3px rgba(76, 141, 255, 0.16);
}

.form-text {
    font-size: 0.75rem;
}

.modal-content {
    border: 1px solid var(--mft-border);
    border-radius: 12px;
}

.modal-header, .modal-footer {
    border-color: var(--mft-border);
}

/* A hairline above the first row of each interface keeps the grouping legible
   without hiding the repeated name that filtering depends on. */
tr.ip-group > td {
    border-top: 1px solid rgba(255, 255, 255, 0.13);
}

/* --------------------------------------------------------- running config */

.cfg-scroll {
    max-height: 74vh;
}

.cfg-table > tbody > tr > td {
    padding: 0.05rem 0.6rem;
    border: 0;
    white-space: pre;
    vertical-align: top;
}

.cfg-table > tbody > tr:hover {
    background: rgba(255, 255, 255, 0.045);
}

.cfg-ln {
    width: 4rem;
    text-align: right;
    color: var(--bs-secondary-color);
    opacity: 0.5;
    font-size: 0.72rem;
    user-select: none;   /* keeps line numbers out of a copy-paste */
}

.cfg-text {
    font-size: 0.8rem;
    line-height: 1.5;
}

/* Warning block inside the confirmation dialog. */
.confirm-warn {
    display: flex;
    gap: 0.7rem;
    align-items: flex-start;
    padding: 0.75rem 0.9rem;
    border: 1px solid rgba(255, 139, 147, 0.3);
    border-radius: var(--mft-radius);
    background: rgba(220, 53, 69, 0.11);
    color: #ffc9cd;
    font-size: 0.85rem;
    line-height: 1.5;
}

.confirm-warn .ico {
    width: 17px;
    height: 17px;
    flex: 0 0 17px;
    margin-top: 0.12rem;
}

/* ------------------------------------------------------- permission grid */

.perm-grid {
    border: 1px solid var(--mft-border);
    border-radius: var(--mft-radius);
    overflow: hidden;
}

.perm-head,
.perm-row {
    display: grid;
    grid-template-columns: 1fr 4.5rem 4.5rem 4.5rem;
    align-items: center;
    gap: 0.25rem;
}

.perm-head {
    background: rgba(0, 0, 0, 0.22);
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.075em;
    color: var(--bs-secondary-color);
    padding: 0.45rem 0.8rem;
}

.perm-row {
    padding: 0.4rem 0.8rem;
    border-top: 1px solid var(--mft-border);
}

.perm-row:hover {
    background: rgba(255, 255, 255, 0.03);
}

.perm-head > span:not(:first-child),
.perm-row > span:not(:first-child) {
    text-align: center;
    position: relative;
}

.perm-name {
    font-size: 0.875rem;
}

/* The radio is small; this makes the whole cell clickable. */
.perm-hit {
    position: absolute;
    inset: -0.4rem 0;
    cursor: pointer;
}

.perm-na {
    color: var(--bs-secondary-color);
    opacity: 0.4;
}

@media (max-width: 575.98px) {
    .perm-head, .perm-row { grid-template-columns: 1fr 3.2rem 3.2rem 3.2rem; }
    .perm-name { font-size: 0.8rem; }
}

/* Simple/Advanced switch inside the ACL rule modal. */
.mode-tabs {
    display: inline-flex;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid var(--mft-border);
    border-radius: 8px;
    padding: 3px;
    gap: 3px;
}

.mode-tabs button {
    border: 0;
    background: transparent;
    color: var(--bs-secondary-color);
    font-size: 0.82rem;
    font-weight: 550;
    padding: 0.3rem 0.85rem;
    border-radius: 6px;
    cursor: pointer;
}

.mode-tabs button.active {
    background: var(--mft-accent);
    color: #fff;
}

/* --------------------------------------------------------------- diff */

pre.diff {
    max-height: 34rem;
    overflow: auto;
    padding: 0.9rem 1rem;
    margin: 0;
    font-size: 0.8rem;
    line-height: 1.45;
    background: rgba(0, 0, 0, 0.28);
    border-radius: 8px;
}

pre.diff span {
    display: block;
    white-space: pre-wrap;
    word-break: break-all;
}

pre.diff .d-add  { color: #75b798; background: rgba(25, 135, 84, 0.13); }
pre.diff .d-del  { color: #ea868f; background: rgba(220, 53, 69, 0.13); }
pre.diff .d-hunk { color: #6ea8fe; font-weight: 600; }
pre.diff .d-meta { color: var(--bs-secondary-color); }

.caps-list {
    max-height: 26rem;
    overflow-y: auto;
    font-size: 0.78rem;
}

/* --------------------------------------------------------------- login */

/* The sign-in page carries no information beyond the product name, so all of
   its visual interest has to come from the surface itself. */

.login-body {
    min-height: 100vh;
    background: #0c0f14;
    overflow: hidden;
}

/* Soft colour wash behind the card. Its own layer so it can sit under the
   content without tinting the card's own translucency. */
.login-glow {
    position: fixed;
    inset: -25%;
    z-index: 0;
    pointer-events: none;
    background:
        radial-gradient(38rem 30rem at 30% 22%, rgba(76, 141, 255, 0.20), transparent 68%),
        radial-gradient(34rem 28rem at 72% 78%, rgba(126, 87, 255, 0.15), transparent 70%),
        radial-gradient(26rem 22rem at 82% 14%, rgba(0, 190, 220, 0.09), transparent 72%);
    filter: blur(14px);
}

.login-shell {
    position: relative;
    z-index: 1;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem 1.25rem;
}

.login-col {
    width: 100%;
    max-width: 23rem;
}

.login-mark {
    text-align: center;
    font-size: 2.1rem;
    font-weight: 680;
    letter-spacing: -0.035em;
    line-height: 1;
    margin-bottom: 1.6rem;
    text-shadow: 0 0 34px rgba(76, 141, 255, 0.28);
}

.login-card {
    border: 1px solid rgba(255, 255, 255, 0.11);
    border-radius: 16px;
    background: rgba(255, 255, 255, 0.045);
    padding: 1.85rem 1.7rem;
    box-shadow:
        0 24px 60px rgba(0, 0, 0, 0.5),
        inset 0 1px 0 rgba(255, 255, 255, 0.07);   /* top edge catches the light */
    backdrop-filter: blur(18px) saturate(130%);
}

.login-card .form-control,
.login-card .btn-outline-secondary {
    background-color: rgba(0, 0, 0, 0.3);
    border-color: rgba(255, 255, 255, 0.12);
}

.login-card .form-control:focus {
    background-color: rgba(0, 0, 0, 0.38);
}

.login-caps {
    font-size: 0.78rem;
    color: #ffcf7a;
}

/* Keep the reveal button the same height as the field beside it. */
.login-card .input-group .btn .ico {
    width: 16px;
    height: 16px;
    display: block;
}

@media (max-width: 575.98px) {
    .login-mark { font-size: 1.85rem; }
    .login-card { padding: 1.5rem 1.25rem; }
}

/* ---------------------------------------------------------- responsive */

@media (max-width: 991.98px) {
    .mft-shell { flex-direction: column; }
    .mft-side {
        width: 100%;
        flex: 0 0 auto;
        height: auto;
        position: static;
        border-right: 0;
        border-bottom: 1px solid var(--mft-border);
    }
    .mft-nav { display: flex; flex-wrap: wrap; gap: 0.2rem; }
    .mft-nav a { flex: 0 0 auto; }
    .mft-nav-label { display: none; }
    .mft-body { padding: 1rem; }
    .table-scroll { max-height: none; }
}

/* Narrow screens: keep Sign out reachable, drop the identity text that would
   otherwise squeeze the page title out. */
@media (max-width: 575.98px) {
    .mft-topbar { padding: 0.7rem 0.9rem; }
    .mft-user-name,
    .mft-user-chip .pill { display: none; }
    .mft-topbar-user { padding-left: 0.55rem; }
}

/* --------------------------------------------------- sortable table heads */

.th-sort {
    cursor: pointer;
    user-select: none;
    position: relative;
    padding-right: 1.1rem !important;
}

.th-sort:hover {
    color: var(--bs-body-color);
}

.th-sort::after {
    content: "↕";           /* up-down arrow: sortable but not sorted */
    position: absolute;
    right: 0.35rem;
    opacity: 0.25;
    font-size: 0.85em;
}

.th-asc::after  { content: "↑"; opacity: 0.9; color: var(--mft-accent); }
.th-desc::after { content: "↓"; opacity: 0.9; color: var(--mft-accent); }

/* One address per line inside the interface row. */
.addr-line {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.08rem 0;
    white-space: nowrap;
}

.addr-line + .addr-line {
    border-top: 1px dashed rgba(255, 255, 255, 0.07);
}

/* Editable description cell. Looks like text until hovered, so a dense table
   does not turn into a wall of buttons, but carries a pencil so the
   affordance is discoverable without hunting the Actions column. */
.desc-edit {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    max-width: 100%;
    background: none;
    border: 1px solid transparent;
    border-radius: 6px;
    padding: 0.1rem 0.4rem;
    margin: -0.1rem -0.4rem;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.desc-edit:hover,
.desc-edit:focus-visible {
    border-color: var(--mft-border);
    background: rgba(255, 255, 255, 0.06);
}

.desc-edit.is-empty .desc-text {
    color: var(--bs-secondary-color);
    font-style: italic;
}

.desc-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.desc-edit .ico {
    width: 12px;
    height: 12px;
    flex: 0 0 12px;
    opacity: 0;
    transition: opacity 0.12s;
}

.desc-edit:hover .ico,
.desc-edit:focus-visible .ico,
.desc-edit.is-empty .ico {
    opacity: 0.65;
}

/* A destructive control needs a real hit area. The old bare "x" was a few
   pixels wide: hard to hit deliberately and easy to hit by accident, which is
   the wrong way round for a button that drops an address off a live router. */
.addr-del {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 26px;
    padding: 0;
    flex: 0 0 auto;
    opacity: 0.8;
}

.addr-del:hover {
    opacity: 1;
}

.addr-del .ico {
    width: 14px;
    height: 14px;
}

/* ------------------------------------------------------- router switcher */

.mft-router-pick .form-select {
    min-width: 13rem;
    /* Reads as an identity chip, not a filter: the accent border matches the
       shell tint so the two always agree about which device is selected. */
    border-color: var(--mft-accent);
    background-color: rgba(0, 0, 0, 0.25);
    font-weight: 600;
}

.mft-router-one {
    font-size: 0.8rem;
    font-weight: 600;
    padding: 0.25rem 0.6rem;
    border: 1px solid var(--mft-accent);
    border-radius: 999px;
    color: var(--bs-body-color);
    white-space: nowrap;
}

.confirm-router-name {
    /* The device name is the one word that must not be skimmed past. */
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* Per-router accent. Managing more than one device from one panel, the colour
   is what tells you at a glance which one you are looking at -- the same
   reason a prod console gets a different background from staging. */
.mft-rt-blue   { --mft-accent: #4c8dff; }
.mft-rt-amber  { --mft-accent: #e0a03c; }
.mft-rt-green  { --mft-accent: #3fb27f; }
.mft-rt-violet { --mft-accent: #9b7cf0; }
.mft-rt-rose   { --mft-accent: #e06c8b; }
.mft-rt-cyan   { --mft-accent: #3fb5c4; }

/* A tinted rule under the header carries the accent across the whole page, so
   it is visible even when the switcher is scrolled out of view. */
.mft-shell .mft-topbar {
    box-shadow: inset 0 -2px 0 var(--mft-accent);
}

/* ------------------------------------------------------------- traffic graph */
/* Headline RX/TX readout. Deliberately the largest thing on the page: when
   you open Traffic the first question is always "what is it doing right now".
   Colours match the chart traces, so this is also the legend. */
.tf-live {
    display: flex;
    align-items: stretch;
    gap: 0.75rem;
    margin-bottom: 0.9rem;
    flex-wrap: wrap;
}

.tf-live-cell {
    flex: 1 1 190px;
    min-width: 170px;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    padding: 0.6rem 0.9rem;
    border-radius: 10px;
    border: 1px solid var(--mft-border);
    background: rgba(255, 255, 255, 0.02);
    /* A tinted left edge ties the tile to its trace in the chart. */
    border-left-width: 4px;
}

.tf-live-cell.tf-rx { border-left-color: #22c55e; }
.tf-live-cell.tf-tx { border-left-color: #4c8dff; }

.tf-live-k {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--bs-body-color);
}
.tf-live-sub {
    font-weight: 400;
    letter-spacing: 0.04em;
    color: var(--bs-secondary-color);
    margin-left: 0.35rem;
}

.tf-live-v {
    font-size: clamp(1.6rem, 3.4vw, 2.4rem);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}
.tf-rx .tf-live-v { color: #3ddc84; }
.tf-tx .tf-live-v { color: #6ba4ff; }

/* While Live is running the tiles glow faintly so it is obvious the numbers
   are current rather than the last point of a history graph. */
.tf-live-cell.is-live.tf-rx { box-shadow: 0 0 0 1px rgba(34, 197, 94, 0.35),
                                          0 0 18px rgba(34, 197, 94, 0.12); }
.tf-live-cell.is-live.tf-tx { box-shadow: 0 0 0 1px rgba(76, 141, 255, 0.35),
                                          0 0 18px rgba(76, 141, 255, 0.12); }

/* Brief flash on each new value so a tick is visible even when it barely moves. */
@keyframes tf-tick {
    0%   { filter: brightness(1.9); }
    100% { filter: brightness(1); }
}
.tf-tick { animation: tf-tick 0.55s ease-out; }

@media (prefers-reduced-motion: reduce) {
    .tf-tick { animation: none; }
}

.tf-legend {
    display: flex;
    align-items: center;
    gap: 1.2rem;
    font-size: 0.8rem;
    color: var(--bs-secondary-color);
    margin-bottom: 0.6rem;
}
.tf-key { display: inline-flex; align-items: center; gap: 0.4rem; }
.tf-swatch { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.tf-swatch-in  { background: #22c55e; }
.tf-swatch-out { background: #4c8dff; }
.tf-stat { margin-left: auto; font-variant-numeric: tabular-nums; }

.tf-chart-wrap { position: relative; width: 100%; }
.tf-chart-wrap canvas {
    width: 100%;
    height: 260px;
    display: block;
}
.tf-tip {
    position: absolute;
    z-index: 5;
    pointer-events: none;
    background: var(--mft-head-bg, #1b1f24);
    border: 1px solid var(--mft-border);
    border-radius: 6px;
    padding: 0.4rem 0.55rem;
    font-size: 0.75rem;
    line-height: 1.5;
    white-space: nowrap;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
}
.tf-tip .tf-tip-t { color: var(--bs-secondary-color); margin-bottom: 0.15rem; }
.tf-tip .tf-swatch { width: 9px; height: 9px; margin-right: 0.3rem; }
.tf-empty {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: var(--bs-secondary-color);
    font-size: 0.85rem;
    padding: 1rem;
}
.tf-summary {
    margin-top: 0.6rem;
    font-size: 0.85rem;
    color: var(--bs-secondary-color);
    font-variant-numeric: tabular-nums;
}

/* Icon-only button (e.g. the traffic-graph link in the interface list). */
.btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.25rem 0.45rem;
    vertical-align: middle;
}
.btn-icon .ico {
    width: 15px;
    height: 15px;
}

/* ------------------------------------------------- native select popups
   A <select>'s open list is drawn by the browser, not by our CSS, so it fell
   back to the OS light scheme: a pale grey list against the dark panel.
   Declaring the colour scheme makes the browser paint the popup (and
   scrollbars) dark; the explicit option/optgroup colours cover engines that
   style the list from CSS instead. */
:root {
    color-scheme: dark;
}

.form-select,
.form-control {
    color-scheme: dark;
}

.form-select option,
.form-select optgroup {
    background-color: #1b1f24;
    color: #e6eaf0;
}

.form-select optgroup {
    color: #93a1b5;
    font-weight: 600;
    font-style: normal;
}

.form-select option:checked,
.form-select option:hover {
    background-color: rgba(76, 141, 255, 0.28);
    color: #ffffff;
}

/* ---- traffic stats row + drag-zoom selection ---- */
.tf-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 0.5rem;
    margin-top: 0.9rem;
    padding-top: 0.8rem;
    border-top: 1px solid var(--mft-border);
}
.tf-stat-cell { display: flex; flex-direction: column; gap: 0.15rem; }
.tf-stat-k {
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--bs-secondary-color);
}
.tf-stat-v {
    font-size: 1rem;
    font-weight: 640;
    font-variant-numeric: tabular-nums;
}
.tf-stat-down.tf-bad .tf-stat-v { color: #ff6b6b; }

.tf-zoombar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.45rem 1rem;
    border-bottom: 1px solid var(--mft-border);
    background: rgba(76, 141, 255, 0.08);
    font-size: 0.8rem;
    color: var(--bs-secondary-color);
}

/* The drag rectangle drawn over the canvas while selecting a range. */
.tf-sel {
    position: absolute;
    top: 0;
    bottom: 26px;
    background: rgba(76, 141, 255, 0.18);
    border-left: 1px solid var(--mft-accent);
    border-right: 1px solid var(--mft-accent);
    pointer-events: none;
}
.tf-chart-wrap canvas { cursor: crosshair; }

/* ---- device health tiles on the System page ---- */
.dev-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 0.9rem 1.2rem;
}
.dev-cell { display: flex; flex-direction: column; gap: 0.18rem; }
.dev-k {
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--bs-secondary-color);
}
.dev-v {
    font-size: 1.35rem;
    font-weight: 650;
    line-height: 1.2;
    letter-spacing: -0.02em;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-variant-numeric: tabular-nums;
}
.dev-v-sm { font-size: 0.95rem; font-weight: 600; letter-spacing: 0; }
.dev-n { font-size: 0.75rem; color: var(--bs-secondary-color); }

/* ---- tools output ---- */
.tool-out {
    margin: 0;
    max-height: 60vh;
    overflow: auto;
    font-size: 0.78rem;
    line-height: 1.45;
    white-space: pre;
    color: var(--bs-body-color);
    background: rgba(0, 0, 0, 0.28);
    border: 1px solid var(--mft-border);
    border-radius: 8px;
    padding: 0.8rem 0.9rem;
}
.dropdown-menu {
    background-color: var(--mft-head-bg);
    border-color: var(--mft-border);
}
.dropdown-item { color: var(--bs-body-color); }
.dropdown-item:hover, .dropdown-item:focus {
    background-color: rgba(76, 141, 255, 0.18);
    color: #fff;
}
.dropdown-header { color: var(--bs-secondary-color); font-size: 0.7rem;
    text-transform: uppercase; letter-spacing: 0.08em; }
