:root {
    --fg: #1c1f26;
    --muted: #5b6472;
    --bg: #fafbfc;
    --panel: #ffffff;
    --line: #e3e6eb;
    --accent: #1a4f8b;
    --accent-soft: #e7eef8;
    --rejected: #c0392b;
    --rejected-soft: #fbe9e7;
    --origin: #d97706;
    --origin-soft: #fef3c7;
    --multipath: #5e35b1;
    --multipath-soft: #ede7f6;
    /* Zebra-stripe tint for tree rows. Slightly darker than --bg so
       it reads as alternation against the page background but stays
       quiet enough not to fight with bar/pill colors. */
    --zebra: #eef1f5;
}

* { box-sizing: border-box; }

body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    color: var(--fg);
    background: var(--bg);
    margin: 0;
    padding: 0;
    font-size: 14px;
    line-height: 1.4;
}

header {
    padding: 20px 28px 16px;
    border-bottom: 1px solid var(--line);
    background: var(--panel);
}

header h1 {
    margin: 0 0 4px 0;
    font-size: 20px;
    color: var(--accent);
    display: flex;
    align-items: center;
    gap: 10px;
}

/* Inline rootbeer-mug SVG icon — sits next to the page title and
   the About modal heading so the report is visually identifiable
   as part of the ROOTBEER suite even without external assets.
   The SVG itself is inlined into the HTML so no extra logo URL has
   to resolve. */
.rootbeer-mug {
    width: 1.4em;
    height: 1.4em;
    flex-shrink: 0;
    display: inline-block;
    vertical-align: middle;
}
.rootbeer-mug-large {
    width: 1.2em;
    height: 1.2em;
    margin-right: 8px;
    vertical-align: -0.18em;
}

.subtitle {
    margin: 0;
    color: var(--muted);
    max-width: 920px;
}

.legend-rejected { color: var(--rejected); font-weight: 600; }
.legend-multipath { color: var(--multipath); font-weight: 600; }
.legend-toggle { color: var(--accent); font-weight: 600; }

.legend-paths-low {
    background: #e7eef8;
    color: #1c1f26;
    padding: 0 4px;
    border-radius: 3px;
    border: 1px solid var(--line);
}
.legend-paths-high {
    background: #062041;
    color: #ffffff;
    padding: 0 4px;
    border-radius: 3px;
}

.legend-multipath-edge {
    color: #5e35b1;
    font-weight: 600;
}

.status {
    margin-top: 8px;
    font-size: 12px;
    color: var(--muted);
}
/* Highlight the BGP-route freshness phrase so the at-a-glance "when
   was this collected?" answer reads cleanly inside the otherwise-
   muted status line. */
.status .status-fresh {
    color: var(--fg);
    font-weight: 600;
}

/* Search bar — single text input with a dropdown underneath. The
   dropdown is absolutely-positioned so the tree below doesn't shift
   as results appear/disappear. */
.search-bar {
    position: relative;
    margin-top: 12px;
    max-width: 560px;
}
#search-input {
    width: 100%;
    padding: 8px 12px;
    border: 1px solid var(--line);
    border-radius: 6px;
    font-size: 13px;
    font-family: inherit;
    background: var(--panel);
    color: var(--fg);
}
#search-input:focus {
    outline: 2px solid var(--accent);
    outline-offset: -1px;
    border-color: var(--accent);
}
.search-results {
    position: absolute;
    z-index: 50;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    max-height: 360px;
    overflow-y: auto;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 6px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
    font-size: 13px;
}
.search-results[hidden] { display: none; }
.search-result {
    padding: 6px 12px;
    cursor: pointer;
    border-bottom: 1px solid var(--line);
}
.search-result:last-child { border-bottom: none; }
.search-result:hover,
.search-result.is-active { background: var(--accent-soft); }
.search-result .sr-primary {
    font-weight: 600;
    color: var(--fg);
}
.search-result .sr-secondary {
    color: var(--muted);
    font-size: 12px;
    margin-top: 1px;
}
.search-result-empty,
.search-result-error {
    padding: 8px 12px;
    color: var(--muted);
    font-style: italic;
}

main {
    padding: 16px 28px 60px;
}

#tree {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 8px 0;
}

ul.tree {
    list-style: none;
    margin: 0;
    padding: 0;
}

ul.tree ul.tree {
    margin-left: 22px;
    border-left: 1px dotted var(--line);
}

li.node { padding: 0; margin: 0; }

.row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 3px 8px 3px 4px;
    border-radius: 4px;
    user-select: none;
}

/* Brief highlight pulse applied to a row by the search-result
   navigation so the user can spot where they just landed. The
   animation fades from a strong accent tint back to transparent
   over 2s; the JS adds the class after scrolling the row into
   view and removes it once the animation finishes. */
@keyframes row-flash-pulse {
    0%   { background: rgba(217, 119, 6, 0.55); }
    20%  { background: rgba(217, 119, 6, 0.45); }
    100% { background: transparent; }
}
.row.row-flash {
    animation: row-flash-pulse 2s ease-out;
}

/* Zebra-stripe rows so the eye can track from the AS pill on the
   left to the numeric columns on the right without losing the line.
   Striping is applied per-sibling within each tree level, so nested
   subtrees also get their own alternating bands — which keeps the
   pattern readable even when the user expands several levels. The
   tint is intentionally subtle so it sits behind the bar/pill colors
   without competing with them. */
li.node:nth-child(even) > .row {
    background: var(--zebra, #f4f6f9);
}
.row:hover {
    background: var(--accent-soft);
}

/* The .name-col wrapper holds the toggle and the AS pill, and
   claims the flex-grow slot so the four numeric columns on the
   right line up vertically across all nesting depths — every .row
   ends at the same right edge regardless of how deeply nested. */
.name-col {
    flex: 1 1 auto;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

/* Numeric columns: fixed width, right-aligned tabular numerals so
   thousands separators align cleanly down each column. */
.col {
    flex: 0 0 80px;
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-size: 12px;
    padding: 1px 6px;
    border-radius: 4px;
}

.col-empty { color: var(--muted); }
.col-accepted { color: var(--accent); }
.col-rejected { color: var(--rejected); }
.col-paths { color: var(--multipath); }
/* "Paths through" — every tree-path that traverses this AS, vs.
   .col-paths which only counts paths terminating here. Tinted
   slightly softer than .col-paths so the eye reads it as a
   superset of the "Paths to" value, not a competing metric. */
.col-paths-through { color: var(--muted-strong, #6c7086); }
/* Multipath cells (paths > 1) are clickable — open the focused
   mini-DAG popup. Match the old multipath-pill background so the
   affordance is unambiguous. */
.col-multipath {
    background: var(--multipath-soft);
    cursor: pointer;
    font-weight: 600;
}
.col-multipath:hover { filter: brightness(0.95); }

/* Column-header row above the tree — same right-edge layout as
   each .row so labels line up with values. */
.tree-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 8px 4px 4px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--line);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
    font-weight: 600;
}
.tree-header .col { color: var(--muted); }
.tree-header .name-col { padding-left: 22px; }

.row.is-root {
    background: var(--accent-soft);
    font-weight: 600;
}

.toggle {
    display: inline-block;
    width: 14px;
    text-align: center;
    color: var(--muted);
    font-family: monospace;
    cursor: pointer;
    flex-shrink: 0;
}

.row.is-leaf .toggle { color: transparent; cursor: default; }

.aspill {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    padding: 0 4px;
    border-radius: 4px;
    cursor: pointer;
}

.aspill:hover { background: var(--accent-soft); }

.row.is-root .aspill { cursor: default; }
.row.is-root .aspill:hover { background: transparent; }

.asn {
    color: var(--accent);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.asname { color: var(--fg); }
.asname.empty { color: var(--muted); font-style: italic; }

.counts {
    margin-left: auto;
    display: flex;
    gap: 10px;
    align-items: center;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.count-pill {
    padding: 1px 6px;
    border-radius: 10px;
    background: var(--accent-soft);
    color: var(--accent);
}

.count-pill.rejected { background: var(--rejected-soft); color: var(--rejected); }
.count-pill.origin { background: var(--origin-soft); color: var(--origin); }

.count-pill.multipath {
    background: var(--multipath-soft);
    color: var(--multipath);
    cursor: pointer;
}

.count-pill.multipath:hover { filter: brightness(0.95); }

.prefix-block {
    margin: 4px 0 8px 22px;
    padding: 8px 12px;
    background: var(--bg);
    border-left: 2px solid var(--origin);
    border-radius: 0 4px 4px 0;
}

.prefix-block h4 {
    margin: 0 0 4px 0;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
}

.prefix-block h4.rejected { color: var(--rejected); }

.prefix-list {
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 12px;
    column-width: 14em;
    column-gap: 16px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.prefix-list li { break-inside: avoid; padding: 1px 0; }
.prefix-list li.rejected { color: var(--rejected); }
.prefix-list .prefix-holder {
    color: var(--muted);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    font-size: 11px;
    margin-left: 0.4em;
}
.prefix-list li.rejected .prefix-holder { color: var(--rejected); opacity: 0.75; }
/* "via X.X.X.X" — the BGP peer interface IP(s) that delivered the
   route. Monospace + slightly muted so it sits next to the holder
   without competing for attention. Multiple IPs are joined with
   middle-dots inline. */
.prefix-list .prefix-peer {
    color: var(--muted);
    font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
    font-size: 11px;
    margin-left: 0.4em;
}
.prefix-list li.rejected .prefix-peer { color: var(--rejected); opacity: 0.75; }

/* Session × prefix matrix table — replaces the per-verdict bulleted
   lists for the focus AS's originated prefixes. Each row is one
   prefix; each session column shows whether that prefix was seen on
   that BGP peering session and which verdict applied (accepted or
   rejected). Rows are sorted by their session-pattern so prefixes
   with identical coverage cluster together; a thin divider marks the
   transition between pattern groups so a row that breaks the common
   shape stands out. */
.prefix-matrix .m-summary {
    font-size: 12px;
    color: var(--muted);
    margin-bottom: 4px;
}
/* The matrix uses a more saturated red than the global --rejected
   token. The default --rejected (#c0392b) reads as muted next to
   the bold, monospace prefix text, which made rejected rows hard
   to scan. --rejected-strong is the same red, deepened for higher
   contrast without changing the global rejected colour anywhere
   else (legend, count pills, h4s, etc.). */
.prefix-matrix {
    --rejected-strong: #b91c1c;
}
.prefix-matrix .m-summary-rej { color: var(--rejected-strong); }
.prefix-matrix .m-toolbar {
    margin-bottom: 6px;
}
.prefix-matrix .m-filterbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 4px 0 8px;
}
.prefix-matrix .m-filter-field,
.flow-pfx-filter-field {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    color: var(--muted);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.prefix-matrix .m-regex-filter,
.flow-pfx-regex-filter {
    width: clamp(120px, 18vw, 220px);
    border: 1px solid var(--line);
    border-radius: 4px;
    padding: 4px 7px;
    color: var(--fg);
    background: var(--panel);
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 12px;
    letter-spacing: 0;
    text-transform: none;
}
.prefix-matrix .m-regex-filter:focus,
.flow-pfx-regex-filter:focus {
    outline: 2px solid var(--accent-soft);
    border-color: var(--accent);
}
.prefix-matrix .m-regex-filter.is-invalid,
.flow-pfx-regex-filter.is-invalid {
    border-color: var(--rejected-strong, #b91c1c);
    outline: 2px solid rgba(185, 28, 28, 0.12);
}
.prefix-matrix .m-filter-clear,
.flow-pfx-filter-clear {
    background: var(--panel);
    color: var(--accent);
    border: 1px solid var(--line);
    border-radius: 4px;
    padding: 4px 9px;
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
    line-height: 1.3;
}
.prefix-matrix .m-filter-clear:hover,
.flow-pfx-filter-clear:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.prefix-matrix .m-filter-status,
.flow-pfx-filter-status {
    color: var(--muted);
    font-size: 11px;
    line-height: 1.4;
}
.prefix-matrix .m-csv-btn {
    background: var(--panel);
    color: var(--accent);
    border: 1px solid var(--line);
    border-radius: 4px;
    padding: 3px 10px;
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
    line-height: 1.4;
}
.prefix-matrix .m-csv-btn:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.prefix-matrix .m-legend {
    font-size: 11px;
    color: var(--muted);
    margin-bottom: 8px;
}
.prefix-matrix .m-legend-cell {
    display: inline-block;
    width: 14px;
    text-align: center;
    font-weight: 600;
}
.prefix-matrix .m-scroll {
    /* Inside the modal layout this wrapper is the sole scroll
       context for the matrix in BOTH axes. The .prefix-matrix-modal
       flex column gives it ``flex: 1; min-height: 0`` so it fills
       the modal body's remaining space — that pins the horizontal
       scrollbar to the bottom of the visible modal area instead of
       the bottom of the (potentially much taller) table, which is
       what makes the right-edge content discoverable. */
    overflow: auto;
    flex: 1;
    min-height: 0;
    /* Force a visible scrollbar even on macOS overlay scrollbars,
       so the user can immediately tell when there's more content
       to the right. Custom-styled to match the report palette. */
    scrollbar-width: thin;
    scrollbar-color: var(--accent) var(--bg);
    /* Right-edge fade gradient as an additional visual cue. Built
       with the local/scroll background-attachment trick (Lea
       Verou): a "blanket" gradient (background-attachment: local)
       moves with the content and covers the static shadow when
       the scroll position is at the matching edge; the shadow
       gradients (default scroll attachment) stay fixed against
       the viewport, so they reveal whichever edge has more
       content beyond it. */
    background:
        linear-gradient(to right, var(--bg), transparent) left center / 14px 100% no-repeat local,
        linear-gradient(to left, var(--bg), transparent) right center / 14px 100% no-repeat local,
        linear-gradient(to right, rgba(0, 0, 0, 0.12), transparent) left center / 14px 100% no-repeat,
        linear-gradient(to left, rgba(0, 0, 0, 0.12), transparent) right center / 14px 100% no-repeat;
}
.prefix-matrix .m-scroll::-webkit-scrollbar {
    height: 10px;
    width: 10px;
}
.prefix-matrix .m-scroll::-webkit-scrollbar-track {
    background: var(--bg);
    border-radius: 5px;
}
.prefix-matrix .m-scroll::-webkit-scrollbar-thumb {
    background: var(--accent);
    border-radius: 5px;
    border: 2px solid var(--bg);
}
.prefix-matrix .m-scroll::-webkit-scrollbar-thumb:hover {
    background: #143a66;
}
.prefix-matrix .m-table {
    /* ``border-collapse: separate`` is required for the sticky
       thead to actually paint over scrolled body rows. With
       ``collapse`` the table's borders are owned at the table
       level and sticky cells lose their backgrounds when scrolled
       out of place in some browsers. ``border-spacing: 0`` keeps
       cells visually flush. */
    border-collapse: separate;
    border-spacing: 0;
    font-size: 12px;
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
.prefix-matrix .m-table th,
.prefix-matrix .m-table td {
    padding: 2px 8px;
    text-align: left;
    border-bottom: 1px solid transparent;
    white-space: nowrap;
}
.prefix-matrix .m-table thead th {
    color: var(--muted);
    font-weight: 600;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border-bottom: 1px solid var(--border, #45475a);
    padding-bottom: 4px;
    /* Lock the header row at the top of the panel-body scroll
       region so it stays visible while the user scrolls through
       a long prefix list. Sticky on individual <th>s (rather than
       <thead>) is the syntax that browsers actually honour for
       table headers. The opaque background hides body rows that
       scroll up underneath; the box-shadow stands in for the
       border-bottom (which sticky elements don't always paint
       cleanly over scrolled content). */
    position: sticky;
    top: 0;
    background: var(--panel);
    z-index: 2;
    box-shadow: 0 1px 0 var(--line);
}
.prefix-matrix .m-table th.m-sess,
.prefix-matrix .m-table td.m-cell {
    text-align: center;
}
/* Session columns use short ``intN`` labels (int0, int1, …) so
   each column stays narrow regardless of whether the underlying
   peer is v4 or v6. Click an intN cell to reveal the full peer
   IP address in a popover anchored to the column header. */
.prefix-matrix .m-table th.m-sess {
    width: 60px;
    min-width: 60px;
    padding: 4px 6px;
    vertical-align: bottom;
    cursor: pointer;
}
.prefix-matrix .m-table th.m-sess:hover {
    background: var(--accent-soft);
}
.prefix-matrix .m-table th.m-sess.is-open {
    background: var(--accent-soft);
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}
.prefix-matrix .m-table th.m-sess .m-sess-int {
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 12px;
    font-weight: 700;
    color: var(--fg);
    text-transform: none;
    letter-spacing: 0;
    text-align: center;
}
/* Address-family suffix attached to intN labels (and echoed in
   the popover). v4 sessions get the report's accent blue; v6
   sessions get a contrasting teal so the two families form a
   visual stripe across the column row. The pill shape distin-
   guishes it from the integer interface index. */
.m-sess-fam {
    display: inline-block;
    margin-left: 3px;
    padding: 0 4px;
    border-radius: 3px;
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    vertical-align: 1px;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.m-sess-fam-v4 { background: var(--accent-soft); color: var(--accent); }
.m-sess-fam-v6 { background: #ccfbf1; color: #0d8a8a; }

/* Floating popover that reveals the peer IP + ASN when a
   session column header is clicked. Lives at document.body root
   so it can escape the table's overflow:auto clipping. */
.m-sess-popover {
    position: fixed;
    z-index: 200;
    background: var(--panel);
    border: 1px solid var(--accent);
    border-radius: 6px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
    padding: 8px 12px;
    font-size: 12px;
    color: var(--fg);
    min-width: 180px;
    pointer-events: auto;
}
.m-sess-popover-row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 2px 0;
}
.m-sess-popover-key {
    color: var(--muted);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    min-width: 70px;
}
.m-sess-popover-val {
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-weight: 600;
}
/* Friendly AS name inside the Peer AS row, rendered in the
   sans-serif body font so it reads as a proper organisation
   name (not a code-style identifier like the AS number itself). */
.m-sess-popover-name {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    font-weight: 500;
    color: var(--muted);
    margin-left: 4px;
}
.prefix-matrix .m-table .m-holder {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    color: var(--muted);
    max-width: 23em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
}
/* Click-to-expand: when a holder name is too long to fit in the
   23em column, the cell is rendered with ellipsis. Clicking it
   toggles is-expanded, which drops the truncation so the full
   holder string is visible (and copy-selectable). Pending /
   empty cells stay non-interactive. */
.prefix-matrix .m-table .m-holder.is-expanded {
    max-width: none;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    word-break: break-word;
}
.prefix-matrix .m-table .m-holder-pending,
.prefix-matrix .m-table .m-holder-empty {
    cursor: default;
}
.prefix-matrix .m-table .m-holder-pending,
.prefix-matrix .m-table .m-holder-empty {
    color: var(--muted);
    opacity: 0.65;
}
/* Three-dot pulsing indicator shown in any holder cell whose name is
   still being resolved by the cached /api/prefix-holders fetch. The
   prefix matrix renders immediately, so this gives a subtle "working"
   cue while RIPE Stat / ARIN lookups complete in the background. */
.holder-fetching {
    display: inline-flex;
    gap: 3px;
    align-items: center;
    vertical-align: middle;
}
.holder-fetching > span {
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.25;
    animation: holderFetchingPulse 1.2s ease-in-out infinite;
}
.holder-fetching > span:nth-child(2) { animation-delay: 0.15s; }
.holder-fetching > span:nth-child(3) { animation-delay: 0.3s; }
@keyframes holderFetchingPulse {
    0%, 80%, 100% { opacity: 0.25; }
    40% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .holder-fetching > span {
        animation: none;
        opacity: 0.6;
    }
}
.prefix-matrix .m-table .m-acc { color: #16a34a; font-weight: 700; }
.prefix-matrix .m-table .m-rej { color: var(--rejected-strong); font-weight: 700; }
.prefix-matrix .m-table .m-none { color: #cbd5e1; }
.prefix-matrix .m-table .m-row.m-row-rej .m-pfx { color: var(--rejected-strong); font-weight: 600; }
.prefix-matrix .m-table .m-row.m-row-rej .m-holder { color: var(--rejected-strong); opacity: 0.85; }
/* Zebra-stripe for prefix rows. Faint enough to stay quiet against
   the rejected / mixed text colours, but visible enough that a
   row of session-cell glyphs reads as a single unit when the
   table is wide. Hover bumps it to a slightly stronger tint so
   the row the cursor is on stands out. */
.prefix-matrix .m-table .m-row.m-row-zebra td { background: var(--zebra); }
.prefix-matrix .m-table .m-row:hover td { background: var(--accent-soft); }
/* Mixed = accepted on some peering sessions and rejected on others.
   Using amber-900 (very dark amber) instead of a yellow tint —
   yellow on white sits below readable contrast for body text. The
   colour still differs enough from the rejected-row red that the
   two states are distinguishable. */
.prefix-matrix .m-table .m-row.m-row-mixed .m-pfx { color: #78350f; font-weight: 700; }
.prefix-matrix .m-table .m-row.m-row-mixed .m-holder { color: #78350f; opacity: 0.85; }
.prefix-matrix .m-table .m-row .m-holder-pending,
.prefix-matrix .m-table .m-row .m-holder-empty {
    color: var(--muted);
    opacity: 0.65;
}
/* Two-line session column header: IP on top, peer AS centered
   below. Both centered so a row of ASes reads cleanly across the
   table when multiple sessions belong to different peers. */
.prefix-matrix .m-table .m-sess-ip {
    text-align: center;
}
/* Three-line column header. ``.m-sess-int`` (top) is the intN
   label with the family pill; ``.m-sess-loc`` (middle) is the
   Internet2 router location code parsed from the service URI;
   ``.m-sess-asn`` (bottom) is the peer ASN. All three centred
   so a row of header columns reads cleanly across the table.
   Empty location / asn lines still take vertical space so columns
   without parsed values stay aligned with neighbours that have
   them. */
.prefix-matrix .m-table .m-sess-loc {
    text-align: center;
    color: var(--fg);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    font-weight: 700;
    font-size: 10px;
    line-height: 1.2;
    min-height: 12px;
    margin-top: 2px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.prefix-matrix .m-table .m-sess-asn {
    text-align: center;
    color: var(--accent, #1a4f8b);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    font-weight: 600;
    font-size: 10px;
    line-height: 1.2;
    min-height: 12px;
    margin-top: 1px;
    text-transform: none;
    letter-spacing: 0;
}
/* Thin divider between session-pattern groups. Rows with the same
   pattern have no top border; the first row of a NEW pattern group
   wears one. The effect is a quiet horizontal line that separates
   blocks of identical-coverage prefixes from blocks that arrived on
   a different set of sessions. */
.prefix-matrix .m-table .m-row.m-row-divider td {
    border-top: 1px dashed var(--border, #45475a);
}
.prefix-matrix .m-table .m-verdict {
    text-align: left;
    color: var(--muted);
}

/* Paths column. Click the cell to toggle a per-prefix expansion
   sub-row that lists every observed AS-path with its verdict. The
   ▾ chevron and a tinted background on hover hint that the cell
   is interactive. */
.prefix-matrix .m-table .m-paths {
    text-align: center;
    color: var(--muted);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.prefix-matrix .m-table .m-paths-clickable {
    cursor: pointer;
    user-select: none;
}
.prefix-matrix .m-table .m-paths-clickable:hover {
    background: var(--accent-soft);
}
.prefix-matrix .m-table .m-paths-clickable.is-open {
    background: var(--accent-soft);
}
.prefix-matrix .m-table .m-paths-count {
    font-weight: 600;
    color: var(--fg);
}
.prefix-matrix .m-table .m-paths-badge {
    display: inline-block;
    margin-left: 4px;
    padding: 0 4px;
    font-size: 11px;
    font-weight: 700;
    border-radius: 3px;
}
.prefix-matrix .m-table .m-paths-mixed {
    background: #fef3c7;
    color: #78350f;
}
.prefix-matrix .m-table .m-paths-rej {
    background: var(--rejected-soft);
    color: var(--rejected-strong);
}
/* Expansion sub-row. Spans every column; renders one line per
   observed AS-path with its verdict + peering-session IPs. */
.prefix-matrix .m-table .m-row-expand td {
    background: var(--bg);
    padding: 6px 12px 8px;
    border-top: 1px dashed var(--line);
}
.prefix-matrix .m-paths-list {
    font-size: 11px;
    line-height: 1.6;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
/* Coverage banner shown above the per-path list when any path
   for the prefix was rejected. Tells the operator whether the
   rejected route is "redundant" (covered by an accepted route)
   or "net loss" (uncovered). Three states: exact-match, covered
   by less-specific, uncovered. Colour-coded green for the two
   covered cases, red for uncovered. */
.prefix-matrix .m-coverage {
    margin-bottom: 6px;
    padding: 6px 10px;
    border-radius: 4px;
    font-size: 11px;
    line-height: 1.45;
}
.prefix-matrix .m-coverage code {
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 11px;
    background: rgba(0, 0, 0, 0.06);
    padding: 0 4px;
    border-radius: 3px;
}
.prefix-matrix .m-coverage .m-cov-icon {
    display: inline-block;
    width: 14px;
    text-align: center;
    font-weight: 700;
    margin-right: 4px;
}
.prefix-matrix .m-cov-exact,
.prefix-matrix .m-cov-loose {
    background: #ecfdf5;
    border: 1px solid #a7f3d0;
    color: #065f46;
}
.prefix-matrix .m-cov-exact .m-cov-icon,
.prefix-matrix .m-cov-loose .m-cov-icon { color: #16a34a; }
.prefix-matrix .m-cov-none {
    background: var(--rejected-soft);
    border: 1px solid #fecaca;
    color: var(--rejected-strong);
}
.prefix-matrix .m-cov-none .m-cov-icon { color: var(--rejected-strong); }
.prefix-matrix .m-pline {
    padding: 1px 0;
}
.prefix-matrix .m-pline .m-pverdict {
    display: inline-block;
    min-width: 5.5em;
    font-weight: 700;
    margin-right: 0.5em;
}
.prefix-matrix .m-pline .m-pverdict-acc { color: #16a34a; }
.prefix-matrix .m-pline .m-pverdict-rej { color: var(--rejected-strong); }
.prefix-matrix .m-pseg {
    color: var(--accent);
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 11px;
}
.prefix-matrix .m-pseg.is-focus {
    color: var(--origin);
    font-weight: 700;
}
.prefix-matrix .m-arrow {
    color: var(--muted);
    margin: 0 0.25em;
}
.prefix-matrix .m-pips {
    color: var(--muted);
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 10px;
    margin-left: 0.5em;
}

/* Open-popup button replacing the inline prefix block. */
.panel-open-prefixes-btn {
    display: block;
    margin: 16px 0 8px;
    background: var(--panel);
    color: var(--accent);
    border: 1px solid var(--line);
    border-radius: 4px;
    padding: 8px 14px;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    text-align: left;
}
.panel-open-prefixes-btn:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.panel-open-prefixes-btn-inline {
    display: inline-flex;
    align-items: center;
    margin: 0;
    padding: 4px 8px;
    font-size: 11px;
    line-height: 1.2;
    white-space: nowrap;
}

/* Modal-hosted matrix: wider card, full-height flex column so
   the matrix-scroll region can fill whatever vertical space is
   left after the summary / toolbar / legend, and the horizontal
   scrollbar stays pinned to the visible bottom of the modal —
   not buried below the table when the table is taller than the
   modal. The sticky thead anchors to .m-scroll (now the sole
   scroll context for both axes) so column headers stay locked
   while the user scrolls within the table. */
.route-modal-card.prefix-matrix-modal {
    width: min(1280px, 96vw);
    height: min(820px, 90vh);
    display: flex;
    flex-direction: column;
}
.route-modal-card.prefix-matrix-modal .route-modal-body {
    overflow: hidden;
    padding: 12px 18px 18px;
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}
.route-modal-card.prefix-matrix-modal .prefix-matrix {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    margin: 0;
}

/* ---------- Side panel (focused mini-DAG) ---------- */

.panel {
    position: fixed;
    top: 0;
    right: 0;
    width: min(820px, 95vw);
    height: 100vh;
    background: var(--panel);
    border-left: 1px solid var(--line);
    box-shadow: -4px 0 20px rgba(0,0,0,0.08);
    transform: translateX(100%);
    transition: transform 0.18s ease-out;
    z-index: 100;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.panel.is-open { transform: translateX(0); }

/* Drag-handle gutter on the left edge of the panel. Sits fully INSIDE
   the panel (the panel itself has overflow:hidden and would otherwise
   clip it), with z-index:3 so it floats above both the header and the
   scrolling body. A faint default stripe + double chevron glyph make
   it discoverable; hovering or actively dragging brightens it.
   Double-click resets to the default width. */
.panel-resize-handle {
    position: absolute;
    top: 0;
    left: 0;
    width: 8px;
    height: 100%;
    cursor: ew-resize;
    z-index: 3;
    background: var(--line);
    transition: background 0.12s, opacity 0.12s;
    display: flex;
    align-items: center;
    justify-content: center;
}
.panel-resize-handle::before {
    content: "⋮⋮";
    color: var(--muted);
    font-size: 11px;
    line-height: 1;
    letter-spacing: -2px;
    pointer-events: none;
}
.panel-resize-handle:hover,
body.panel-resizing .panel-resize-handle {
    background: var(--accent);
}
.panel-resize-handle:hover::before,
body.panel-resizing .panel-resize-handle::before {
    color: #ffffff;
}
/* While a drag is in progress, suppress text selection and force
   the resize cursor everywhere — otherwise dragging fast across
   the page picks up arbitrary text selections. */
body.panel-resizing,
body.panel-resizing * {
    user-select: none !important;
    cursor: ew-resize !important;
}

.panel-header {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 18px;
    border-bottom: 1px solid var(--line);
    background: var(--accent-soft);
}

.panel-title {
    flex: 1;
    font-size: 16px;
    font-weight: 600;
    color: var(--accent);
}

.panel-title .meta {
    color: var(--muted);
    font-weight: 400;
    font-size: 13px;
    margin-left: 8px;
}

.panel-title .meta.multipath {
    color: var(--multipath);
    font-weight: 600;
}

#panel-close {
    background: transparent;
    border: none;
    color: var(--muted);
    font-size: 24px;
    line-height: 1;
    cursor: pointer;
    padding: 0 6px;
}

#panel-close:hover { color: var(--fg); }

.panel-body {
    flex: 1;
    overflow: auto;
    padding: 12px 18px 32px;
}

.panel-loading { color: var(--muted); font-style: italic; padding: 8px 0; }

.dag-wrap {
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 8px;
    margin-bottom: 12px;
}

.cy-container {
    width: 100%;
    min-height: 220px;
    background: var(--bg);
}

.sankey-container {
    width: 100%;
    min-height: 240px;
    overflow: auto;
    background: var(--bg);
}

svg.sankey {
    display: block;
    user-select: none;
}
/* Browsers default to sub-pixel-positioned text for the canvas,
   which renders the AS / AS-name labels with a soft edge —
   noticeable next to the crisp ribbons. text-rendering:
   geometricPrecision tells the browser to lay glyphs out on the
   true geometric grid (matching what a standalone SVG viewer does
   when you download the file). The font-smoothing pair removes
   the legacy macOS subpixel-AA bleed that further softens text on
   light backgrounds. */
svg.sankey text {
    text-rendering: geometricPrecision;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

svg.sankey .sankey-node {
    fill: var(--accent);
    stroke: #0d3a6e;
    stroke-width: 1;
}

svg.sankey .sankey-node.is-root {
    fill: var(--accent);
    stroke: #0d3a6e;
}

svg.sankey .sankey-node.is-focus {
    fill: var(--origin);
    stroke: #b35a04;
}

svg.sankey .sankey-link {
    fill: var(--accent);
    fill-opacity: 0.32;
    stroke: none;
    transition: fill-opacity 0.12s;
}

svg.sankey .sankey-link.rejected {
    fill: var(--rejected);
    fill-opacity: 0.42;
}

svg.sankey .sankey-link:hover { fill-opacity: 0.7; }

svg.sankey .sankey-label {
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 11px;
    fill: var(--accent);
    font-weight: 600;
    pointer-events: none;
}

svg.sankey .sankey-label.is-focus { fill: var(--origin); }
svg.sankey .sankey-label.is-root { fill: var(--accent); }

/* Direction-of-flow indicator below the Sankey: a label on the left
   ("Routes flow this direction") and a long arrow on the right
   spanning the rest of the diagram width. The whole bar's width is
   set inline by the renderer to match the SVG so it lines up
   visually with the chart above it. */
.sankey-flowbar {
    margin-top: 6px;
    display: flex;
    align-items: center;
    gap: 10px;
    color: var(--muted);
    font-size: 12px;
    user-select: none;
}
.sankey-flowbar-label {
    flex-shrink: 0;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 600;
}
.sankey-flowbar-track {
    flex: 1;
    position: relative;
    height: 14px;
    display: flex;
    align-items: center;
}
.sankey-flowbar-line {
    position: absolute;
    left: 0;
    right: 14px;
    height: 2px;
    background: var(--accent);
    border-radius: 1px;
}
.sankey-flowbar-arrow {
    margin-left: auto;
    color: var(--accent);
    font-size: 14px;
    line-height: 1;
    transform: translateY(-1px);
}

.sankey-legend {
    margin-top: 8px;
    padding: 8px 10px;
    display: flex;
    flex-wrap: wrap;
    gap: 4px 14px;
    align-items: center;
    font-size: 11px;
    color: var(--muted);
    border-top: 1px solid var(--line);
    background: var(--panel);
    border-radius: 0 0 6px 6px;
}

.sankey-legend .legend-title {
    font-weight: 600;
    color: var(--fg);
    margin-right: 6px;
}

.sankey-legend .legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--fg);
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 11px;
    margin-right: 18px;
    margin-bottom: 4px;
}

.sankey-legend .legend-swatch {
    width: 12px;
    height: 12px;
    border-radius: 2px;
    border: 1px solid rgba(0,0,0,0.15);
    display: inline-block;
}

.sankey-legend .legend-split-bar {
    display: inline-flex;
    flex-direction: column;
    width: 14px;
    height: 18px;
    border: 1px solid rgba(0,0,0,0.2);
    border-radius: 2px;
    overflow: hidden;
    margin-right: 4px;
    vertical-align: middle;
}

.sankey-legend .legend-split-acc {
    flex: 0 0 60%;
    background: var(--accent);
}

.sankey-legend .legend-split-rej {
    flex: 0 0 40%;
    background: var(--rejected);
}

/* ---------- Network page ---------- */

.controls {
    margin-top: 10px;
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
}

.controls button {
    background: var(--panel);
    border: 1px solid var(--line);
    color: var(--fg);
    padding: 4px 10px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 13px;
}

.controls button:hover { border-color: var(--accent); color: var(--accent); }

.controls .back-link {
    color: var(--accent);
    text-decoration: none;
    font-size: 13px;
    padding: 4px 8px;
}

.controls .back-link:hover { text-decoration: underline; }

.controls #status { margin-left: auto; }

main.network-main {
    padding: 0;
    height: calc(100vh - 170px);
    min-height: 480px;
}

#graph {
    width: 100%;
    height: 100%;
    background: var(--bg);
    border-top: 1px solid var(--line);
}

.network-main .edge-tip { white-space: normal; max-width: 280px; }

.edge-tip {
    position: fixed;
    background: #222;
    color: white;
    font-size: 11px;
    padding: 4px 8px;
    border-radius: 4px;
    pointer-events: none;
    z-index: 200;
    white-space: nowrap;
    display: none;
}

.path-list {
    margin: 0 0 12px 0;
    padding: 0;
    list-style: none;
}

.path-list li {
    padding: 4px 0;
    border-bottom: 1px solid var(--line);
    font-size: 12px;
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    color: var(--muted);
}

.path-list li:last-child { border-bottom: none; }

.path-list .pseg {
    color: var(--fg);
    padding: 1px 3px;
    border-radius: 3px;
}

.path-list .pseg.is-focus {
    background: var(--origin-soft);
    color: var(--origin);
    font-weight: 600;
}

.path-transit-tag {
    color: var(--muted);
    font-style: italic;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    font-size: 11px;
}

.panel-section-h {
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
    margin: 12px 0 6px 0;
}

/* Route popup — overlay shown when an AS bar in the Sankey is
   clicked. Floats on top of the focused-AS panel; dismissed via
   close button, backdrop click, or Escape. The card is centred and
   bounded so a long route list scrolls inside it instead of pushing
   the rest of the page around. */
.route-modal-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    z-index: 200;
    align-items: center;
    justify-content: center;
    padding: 24px;
}
.route-modal-overlay.is-open { display: flex; }
.route-modal-card {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 8px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
    max-width: 1100px;
    width: 100%;
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.route-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    padding: 14px 18px;
    background: var(--accent-soft);
    border-bottom: 1px solid var(--line);
}
.route-modal-title {
    color: var(--accent);
    font-size: 16px;
    font-weight: 600;
}
.route-modal-close {
    background: transparent;
    border: 0;
    color: var(--muted);
    font-size: 24px;
    line-height: 1;
    cursor: pointer;
    padding: 0 8px;
    border-radius: 4px;
    transition: background 0.12s, color 0.12s;
}
.route-modal-close:hover {
    color: var(--fg);
    background: rgba(0, 0, 0, 0.06);
}
.route-modal-body {
    overflow: auto;
    padding: 12px 18px 18px;
}
.route-modal-section-h {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin: 14px 0 4px;
    font-size: 13px;
    font-weight: 600;
    color: var(--accent);
}
.route-modal-section-h:first-child { margin-top: 0; }
.route-modal-section-h .meta {
    color: var(--muted);
    font-weight: 400;
    font-size: 0.92em;
}
.route-modal-empty {
    color: var(--muted);
    font-style: italic;
    padding: 8px 0;
}
.route-modal-scroll {
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--panel);
    overflow: auto;
}
/* Suppress page-level scroll while the popup is open. */
body.route-modal-open { overflow: hidden; }
.route-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12px;
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
.route-table thead th {
    position: sticky;
    top: 0;
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 600;
    text-align: left;
    padding: 6px 10px;
    border-bottom: 1px solid var(--line);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.route-table tbody td {
    padding: 4px 10px;
    border-bottom: 1px solid var(--line);
    vertical-align: top;
}
.route-table tbody tr:last-child td { border-bottom: none; }
.route-table tbody tr.rejected td { background: var(--rejected-soft); }
.route-table tbody tr.rejected .rt-status {
    color: var(--rejected);
    font-weight: 600;
}
.route-table tbody tr.accepted .rt-status { color: var(--accent); }
.route-table .rt-prefix { white-space: nowrap; font-weight: 500; }
.route-table .rt-holder {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    color: var(--fg);
    max-width: 22em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.route-table .rt-holder-none {
    color: var(--muted);
    font-style: italic;
}
.route-table .rt-status { text-transform: capitalize; }
.route-table .rt-peer-ip {
    font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
    font-size: 11.5px;
    color: var(--fg);
    white-space: nowrap;
}
.route-table .rt-origin { white-space: nowrap; color: var(--fg); }
.route-table .rt-path {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    font-size: 11.5px;
    line-height: 1.5;
}
.route-table .rt-as { color: var(--muted); }
.route-table .rt-as-hl {
    color: var(--origin);
    font-weight: 700;
}
/* AS-path prepending: highlight hops the announcing AS prepended
   itself, annotated with × the consecutive-run length. The orange
   matches the "AS-path determined" colour used elsewhere for
   prepending-related signal. */
.route-table .rt-as-prep {
    color: #fab387;
    background: rgba(250, 179, 135, 0.13);
    padding: 1px 5px;
    border-radius: 3px;
    font-weight: 600;
}
/* Best-path indicator on a prefix row — re-info reported this
   exact (path, prefix) tuple as Internet2's chosen best-path. The
   blue calls out the "we actually use this" status without competing
   with the route accept/reject colour scheme. */
.route-table .rt-best-badge {
    display: inline-block;
    background: #89b4fa;
    color: #1e1e2e;
    font-size: 0.72em;
    font-weight: 700;
    padding: 1px 5px;
    border-radius: 3px;
    margin-left: 6px;
    letter-spacing: 0.3px;
}
/* BGP community badges — palette comes from data.community_labels
   (mirrors the Global LPP report's colour assignments). */
.route-table .rt-comm-badge {
    display: inline-block;
    color: #1e1e2e;
    font-size: 0.72em;
    font-weight: 700;
    padding: 1px 6px;
    border-radius: 3px;
    margin-left: 6px;
    cursor: help;
}
/* Visually mute non-best-path rows when the panel is showing every
   observed route — the best-path rows pop without needing a positive
   highlight. (The tag itself stays full opacity.) */
.route-table tr.is-not-best td.rt-prefix,
.route-table tr.is-not-best td.rt-holder,
.route-table tr.is-not-best td.rt-status,
.route-table tr.is-not-best td.rt-peer-ip,
.route-table tr.is-not-best td.rt-origin,
.route-table tr.is-not-best td.rt-path {
    opacity: 0.6;
}
.route-table .rt-arrow { color: var(--line); margin: 0 2px; }

/* Sankey section header carries the toggles and download buttons on
   the right. Switch the header to a flex row so the title
   and the actions sit on the same baseline regardless of header
   width. */
.panel-section-h.sankey-section-h {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.sankey-title-group {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    min-width: 0;
    flex-wrap: wrap;
}
.sankey-section-actions {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-shrink: 0;
}
.sankey-upstream-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 11px;
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
    color: var(--muted);
    cursor: pointer;
    user-select: none;
    padding: 2px 6px;
    border-radius: 4px;
    transition: color 0.12s, background 0.12s;
}
.sankey-upstream-toggle:hover {
    color: var(--accent);
    background: var(--accent-soft);
}
.sankey-upstream-toggle input {
    margin: 0;
    accent-color: var(--accent);
    cursor: pointer;
}
/* Disabled toggle (e.g. "Best paths only" while re-info hasn't
   landed yet) — muted text, no hover affordance, default cursor on
   the underlying input. The tooltip explains why. */
.sankey-upstream-toggle.is-disabled {
    opacity: 0.55;
    cursor: not-allowed;
}
.sankey-upstream-toggle.is-disabled:hover {
    color: var(--muted);
    background: transparent;
}
.sankey-upstream-toggle.is-disabled input {
    cursor: not-allowed;
}
.sankey-export-btn {
    background: var(--bg);
    border: 1px solid var(--border, #c8d0dc);
    color: var(--accent);
    font-size: 11px;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0;
    padding: 3px 8px;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
}
.sankey-export-btn:hover {
    background: var(--accent);
    color: #ffffff;
    border-color: var(--accent);
}
.sankey-export-btn:disabled {
    opacity: 0.6;
    cursor: wait;
}
.sankey-export-btn:disabled:hover {
    background: var(--bg);
    color: var(--accent);
    border-color: var(--border, #c8d0dc);
}
.sankey-export-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.sankey-help-btn,
.sankey-zoom-btn {
    background: var(--bg);
    border: 1px solid var(--border, #c8d0dc);
    color: var(--accent);
    font-size: 11px;
    font-weight: 600;
    padding: 3px 8px;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
}
.sankey-help-btn:hover,
.sankey-zoom-btn:hover {
    background: var(--accent);
    color: #ffffff;
    border-color: var(--accent);
}
.sankey-help-btn:focus-visible,
.sankey-zoom-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Hop-limit slider in the Sankey section header. Inline label +
   range input that caps how many AS-hops from AS11537 the diagram
   shows. Slider pinned right reads "All". */
.sankey-hop-slider {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    color: var(--muted);
    user-select: none;
    cursor: pointer;
}
.sankey-hop-slider span {
    font-weight: 600;
    color: var(--fg);
    min-width: 4.5em;
}
.sankey-hop-slider input[type="range"] {
    width: 90px;
    accent-color: var(--accent);
    cursor: pointer;
}

/* "How to read this" help modal — prose-only explanation of how
   flows / ribbons / colors are organised. Uses the project's light
   theme tokens (--fg, --muted, --accent) so the text reads cleanly
   against the white modal background. */
.sankey-help-modal { width: min(94vw, 720px); max-height: 86vh; }
.sankey-help-body {
    padding: 1em 1.4em 1.4em;
    overflow: auto;
    color: var(--fg);
}
.sankey-help-body h4 {
    margin: 1.1em 0 0.4em;
    font-size: 0.98em;
    font-weight: 700;
    color: var(--accent);
}
.sankey-help-body h4:first-child { margin-top: 0; }
.sankey-help-body p,
.sankey-help-body ul {
    margin: 0 0 0.7em;
    color: var(--fg);
    font-size: 0.94em;
    line-height: 1.55;
}
.sankey-help-body ul { padding-left: 1.3em; }
.sankey-help-body li { margin-bottom: 0.4em; }
.sankey-help-body code {
    background: var(--bg);
    border: 1px solid var(--line);
    padding: 0 5px;
    border-radius: 3px;
    font-size: 0.88em;
}
.sankey-help-body strong { color: #1c1f26; font-weight: 700; }

/* Per-flow selection state. Earlier iterations tried "dim hard"
   (2-5% opacity on non-selected) but a thin selected flow couldn't
   visually compete with thick dimmed neighbors at shared nodes.
   Switched to fully hiding everything that isn't the selected flow:
   non-selected ribbons get visibility:hidden (so they don't draw
   but the SVG layout stays put), and off-path AS nodes hide too,
   leaving the path's AS nodes and its single chain of ribbons
   visible. On-path nodes get an outline so the path's stops are
   easy to read. */
/* Selection-disabled banner above the SVG. Shown when the diagram
   has too many distinct paths to highlight interactively (the
   threshold lives in renderSankeyDAG). */
.sankey-selection-disabled-notice {
    background: #fef9c3;
    border-left: 4px solid #ca8a04;
    color: #713f12;
    padding: 8px 12px;
    border-radius: 4px;
    margin: 0 0 8px 0;
    font-size: 13px;
    line-height: 1.45;
}
.sankey-selection-disabled-notice strong { color: #713f12; }
.sankey-selection-disabled-notice em {
    font-style: normal;
    font-weight: 600;
}
/* In selection-disabled mode, remove ribbons from pointer hit-testing
   entirely. That prevents the browser and JS picker from doing
   expensive "which SVG path was clicked?" work across thousands of
   ribbons when selection is intentionally unavailable. */
svg.sankey.selection-disabled .sankey-segment {
    cursor: default;
    pointer-events: none;
}
.sankey-segment {
    cursor: pointer;
    /* No transition: with thousands of ribbons in a large topology,
       a fill-opacity / opacity transition fires a transitionend per
       ribbon on every selection change, which queued enough events
       to make the page unresponsive. The dimmed state is
       visibility:hidden anyway, so there's nothing meaningful to
       animate. */
}
/* Non-selected ribbons live in linksGBase (selected ones move to
   linksGTop). When a selection is active, applySelection sets
   visibility:hidden on linksGBase directly — one DOM mutation
   hides every non-selected ribbon via inheritance, with no
   per-element style recalc or paint invalidation. The earlier
   approach used `svg.has-selection .sankey-segment:not(.is-selected)`
   which read like a single rule but still forced the browser to
   evaluate the selector against every .sankey-segment, putting an
   O(N) cost back in the style engine. */
.sankey-segment.is-selected {
    fill-opacity: 0.95 !important;
    stroke: #cdd6f4;
    stroke-width: 1.5;
}

svg.sankey.has-selection .sankey-node:not(.is-on-path),
svg.sankey.has-selection .sankey-node-labels:not(.is-on-path) {
    visibility: hidden;
}
svg.sankey .sankey-node.is-on-path rect,
svg.sankey .sankey-node.is-on-path .sankey-node-bar {
    stroke: #cdd6f4;
    stroke-width: 1.5;
}

/* About modal — keyed off the same light palette as the rest of
   the page (white panel, blue accent, muted grey text) so it
   matches the live page rather than the dark dialog used in the
   ASPA-graph service. Triggered by the "About this report" link in
   the header. */
.about-modal {
    position: fixed;
    inset: 0;
    z-index: 1000;
    background: rgba(15, 23, 42, 0.45);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 4vh 16px;
    overflow-y: auto;
}
.about-modal[hidden] { display: none; }
.about-modal-content {
    position: relative;
    background: var(--panel);
    color: var(--fg);
    border: 1px solid var(--line);
    border-radius: 8px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);
    max-width: 760px;
    width: 100%;
    padding: 32px 36px 28px;
    line-height: 1.55;
}
.about-modal-content h2 {
    margin: 0 0 12px;
    font-size: 22px;
    color: var(--accent);
}
.about-modal-content h3 {
    margin: 22px 0 6px;
    font-size: 15px;
    color: var(--fg);
    border-bottom: 1px solid var(--line);
    padding-bottom: 4px;
}
.about-modal-content p { margin: 8px 0; }
.about-modal-content code {
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 3px;
    padding: 0 4px;
    font-size: 0.92em;
}
.about-modal-content a { color: var(--accent); text-decoration: none; }
.about-modal-content a:hover { text-decoration: underline; }
.about-modal-content ul { margin: 6px 0 12px 18px; padding: 0; }
.about-modal-content ul ul { margin-top: 4px; margin-bottom: 4px; }
.about-modal-content li { margin: 4px 0; }
.about-modal-close {
    position: absolute;
    top: 10px;
    right: 14px;
    border: none;
    background: none;
    font-size: 26px;
    line-height: 1;
    color: var(--muted);
    cursor: pointer;
    padding: 4px 8px;
    border-radius: 4px;
}
.about-modal-close:hover { color: var(--fg); background: var(--bg); }
.about-logos {
    display: flex;
    align-items: center;
    gap: 28px;
    margin: 6px 0 18px;
    padding: 14px 18px;
    /* Dark backdrop for the Internet2 wordmark (which is the
       white-on-transparent variant from internet2.edu). The CAIDA
       logo gets its own white tile via .logo-caida below so its
       grey-on-white image still has contrast. */
    background: var(--accent);
    border-radius: 6px;
}
.about-logos a { display: inline-flex; align-items: center; }
.about-logos img { height: 36px; width: auto; display: block; }
.about-logos .logo-i2 { height: 30px; }
.about-logos .logo-caida {
    height: 48px;
    background: #ffffff;
    padding: 4px 8px;
    border-radius: 4px;
}
.about-lead { font-size: 14px; }
.about-nsf-notice {
    margin-top: 22px;
    padding: 12px 14px;
    background: var(--bg);
    border-left: 3px solid var(--accent);
    border-radius: 4px;
    font-size: 12.5px;
    color: var(--muted);
    line-height: 1.5;
}

/* Data Sources modal — reuses .about-modal{,-content} for the
   overlay/box and adds a compact, monospace-ish freshness table. */
.data-sources-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 12px;
    font-size: 13px;
}
.data-sources-table th,
.data-sources-table td {
    text-align: left;
    padding: 8px 10px;
    border-bottom: 1px solid var(--border);
    vertical-align: top;
}
.data-sources-table th {
    color: var(--muted);
    font-weight: 600;
    font-size: 11.5px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.data-sources-table th.num,
.data-sources-table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.data-sources-table .ds-rel {
    color: var(--muted);
    font-size: 11.5px;
}

.sankey-flow-readout {
    display: flex;
    align-items: center;
    gap: 0.6em;
    padding: 0.4em 0.8em;
    margin: 0.4em 0;
    background: #1e1e2e;
    border: 1px solid #45475a;
    border-radius: 4px;
    font-size: 0.9em;
    color: #cdd6f4;
    flex-wrap: wrap;
}
.flow-readout-label {
    color: #a6adc8;
    text-transform: uppercase;
    font-size: 0.78em;
    letter-spacing: 0.5px;
}
.flow-readout-path {
    font-family: monospace;
    color: #cdd6f4;
}
.flow-readout-as {
    color: #89b4fa;
    cursor: pointer;
    text-decoration: none;
}
.flow-readout-as:hover { text-decoration: underline; }
.flow-readout-arrow { color: #585b70; margin: 0 0.15em; }
.flow-readout-status {
    padding: 1px 8px;
    border-radius: 10px;
    font-size: 0.78em;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 600;
}
.flow-readout-status.accepted { background: #a6e3a133; color: #a6e3a1; }
.flow-readout-status.rejected { background: #f38ba833; color: #f38ba8; }
.flow-readout-prefixes { color: #a6adc8; }
/* Rejected-route coverage summary on the readout strip — at-a-glance
   "X of Y prefixes also reach AS11537 via accepted routes" so an
   operator can tell apart redundant rejections from net loss
   without opening the modal. */
.flow-readout-coverage { color: #a6adc8; font-size: 0.88em; }
.flow-readout-uncovered { color: #f38ba8; font-weight: 600; }
/* Clickable variant in the readout strip — pops the per-AS-path
   prefix table. Uses the readout strip's own dark-theme palette
   (light-blue accent on transparent, matching .flow-readout-as)
   so the button reads against the dark #1e1e2e strip background. */
.flow-readout-prefixes-btn {
    background: transparent;
    border: 1px solid #45475a;
    color: #89b4fa;
    padding: 1px 8px;
    border-radius: 10px;
    cursor: pointer;
    font: inherit;
    font-size: 0.92em;
    text-decoration: underline;
    text-decoration-color: rgba(137, 180, 250, 0.4);
    text-underline-offset: 2px;
}
.flow-readout-prefixes-btn:hover {
    background: #313244;
    color: #cdd6f4;
    border-color: #585b70;
    text-decoration-color: #cdd6f4;
}
.flow-readout-prefixes-btn:focus-visible {
    outline: 2px solid #89b4fa;
    outline-offset: 2px;
}

/* Per-AS-path prefix-listing modal — opened from the readout
   strip's clickable prefix count. Header shows the AS-path and
   accepted/rejected status; body is a two-column table of
   (prefix, holder). */
.flow-pfx-modal {
    width: min(92vw, 720px);
    height: min(720px, 86vh);
}
.flow-pfx-modal .route-modal-body {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}
.flow-pfx-modal .route-modal-scroll {
    flex: 1;
    min-height: 0;
}
.flow-pfx-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5em;
    font-size: 0.94em;
}
.flow-pfx-title-label {
    color: var(--muted, #5b6472);
    font-size: 0.82em;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.flow-pfx-title-path {
    font-family: monospace;
    color: var(--fg, #1c1f26);
}
.flow-pfx-title-count {
    color: var(--muted, #5b6472);
    font-size: 0.88em;
}
.flow-pfx-filterbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 0 0 8px;
}
.flow-pfx-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.92em;
}
.flow-pfx-table th {
    text-align: left;
    padding: 0.4em 0.7em;
    background: var(--bg, #fafbfc);
    border-bottom: 1px solid var(--line, #e3e6eb);
    color: var(--muted, #5b6472);
    font-weight: 600;
    font-size: 0.82em;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    position: sticky;
    top: 0;
}
.flow-pfx-table td {
    padding: 0.35em 0.7em;
    border-bottom: 1px solid var(--zebra, #eef1f5);
    vertical-align: top;
    color: var(--fg, #1c1f26);
}
/* Section header row inserted between flows in the aggregated
   prefix list. Shows the AS-path as it changes, plus the
   flow's kind chip and prefix count. */
.flow-pfx-section-row td {
    padding: 0.55em 0.7em 0.35em;
    background: var(--bg, #fafbfc);
    border-top: 1px solid var(--line, #e3e6eb);
    border-bottom: 1px solid var(--line, #e3e6eb);
    font-size: 0.92em;
    color: var(--muted, #5b6472);
    position: sticky;
    top: 2.1em;
    z-index: 1;
}
.flow-pfx-section-kind {
    display: inline-block;
    padding: 0.1em 0.55em;
    border-radius: 10px;
    font-size: 0.78em;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    margin-right: 8px;
}
.flow-pfx-section-kind.accepted {
    background: rgba(26, 79, 139, 0.12);
    color: var(--accent, #1a4f8b);
}
.flow-pfx-section-kind.rejected {
    background: rgba(192, 57, 43, 0.12);
    color: var(--rejected, #c0392b);
}
.flow-pfx-section-path {
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 0.95em;
    color: var(--fg, #1c1f26);
    margin-right: 10px;
}
.flow-pfx-section-count {
    font-size: 0.85em;
    color: var(--muted, #5b6472);
}
/* "Received via" column for the aggregated prefix list.
   Per-prefix list of peering-session badges (peer IP + family +
   router location). Compact so multi-session prefixes still
   read on one or two rows; family colour matches the matrix
   view (blue for v4, teal for v6). */
.flow-pfx-cell-peers {
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 0.85em;
    color: var(--fg, #1c1f26);
}
.flow-pfx-peer-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 1px 6px;
    margin: 1px 4px 1px 0;
    border-radius: 4px;
    background: var(--bg, #fafbfc);
    border: 1px solid var(--line, #e3e6eb);
}
.flow-pfx-peer-fam {
    display: inline-block;
    font-size: 0.78em;
    font-weight: 700;
    text-transform: uppercase;
    padding: 0 4px;
    border-radius: 3px;
    color: #fff;
}
.flow-pfx-peer-v4 .flow-pfx-peer-fam { background: #1a4f8b; }
.flow-pfx-peer-v6 .flow-pfx-peer-fam { background: #0d8a8a; }
.flow-pfx-peer-ip {
    color: var(--fg, #1c1f26);
}
.flow-pfx-peer-loc {
    color: var(--muted, #5b6472);
    font-size: 0.92em;
}
.flow-pfx-peer-loc::before {
    content: "@ ";
    color: var(--muted, #5b6472);
}
.flow-pfx-cell-pfx {
    font-family: monospace;
    white-space: nowrap;
}
.flow-pfx-cell-holder {
    color: var(--muted, #5b6472);
    word-break: break-word;
}
/* "Covered by" column for rejected-route prefix tables. Distinguishes
   covered (=  prefix is also accepted via another path on the
   diagram) from uncovered (= net loss, only this rejected path
   carries the prefix). */
.flow-pfx-cell-cover {
    color: var(--fg, #1c1f26);
    font-size: 0.92em;
}
.flow-pfx-cell-cover code {
    background: var(--bg, #fafbfc);
    border: 1px solid var(--line, #e3e6eb);
    padding: 0 4px;
    border-radius: 3px;
    font-size: 0.92em;
}
.flow-pfx-cell-uncovered {
    color: var(--rejected, #c0392b);
    font-weight: 600;
}
.flow-pfx-cover-as {
    color: var(--accent, #1a4f8b);
    font-family: monospace;
    font-weight: 600;
}
.flow-pfx-title-coverage {
    color: var(--muted, #5b6472);
    font-size: 0.88em;
    padding: 1px 8px;
    border-radius: 10px;
    background: var(--bg, #fafbfc);
    border: 1px solid var(--line, #e3e6eb);
}
.flow-readout-clear {
    margin-left: auto;
    background: transparent;
    border: 1px solid #45475a;
    color: #a6adc8;
    width: 22px;
    height: 22px;
    border-radius: 11px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    line-height: 1;
}
.flow-readout-clear:hover {
    background: #313244;
    color: #cdd6f4;
}

/* AS-bar click popover — two-button menu offering "highlight every
   flow through this AS" or "list flows through this AS". Position is
   set inline at the click location; sizing and chrome live here.
   Floats above the SVG via fixed positioning so it isn't clipped by
   the panel's overflow. */
.sankey-as-popover {
    position: fixed;
    z-index: 1000;
    background: #1e1e2e;
    border: 1px solid #45475a;
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
    padding: 0.3em 0;
    min-width: 220px;
    color: #cdd6f4;
    font-size: 0.92em;
}
.sankey-as-popover-header {
    padding: 0.45em 0.9em;
    border-bottom: 1px solid #313244;
    color: #a6adc8;
    font-size: 0.82em;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 600;
}
.sankey-as-popover-btn {
    display: block;
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    color: #cdd6f4;
    padding: 0.5em 0.9em;
    font: inherit;
    cursor: pointer;
}
.sankey-as-popover-btn:hover {
    background: #313244;
    color: #f5c2e7;
}
.sankey-as-popover-icon {
    color: #6c7086;
    margin-right: 0.4em;
}

/* Ribbon picker popover. Same dark-theme chrome as the AS-action
   popover, but rows include a colour swatch and a one-line
   summary (kind / origin → last hop → AS11537 / prefix count) so
   the user can identify the right ribbon when several stack at
   the click point. */
.sankey-ribbon-picker { min-width: 320px; }
.sankey-ribbon-picker .ribbon-pick-btn {
    display: flex;
    align-items: center;
    gap: 0.5em;
    font-size: 0.88em;
    padding: 0.4em 0.9em;
    white-space: nowrap;
}
.sankey-ribbon-picker .ribbon-pick-swatch {
    display: inline-block;
    width: 14px;
    height: 14px;
    border-radius: 2px;
    flex: 0 0 auto;
    border: 1px solid #45475a;
}
.sankey-ribbon-picker .ribbon-pick-kind {
    text-transform: uppercase;
    font-size: 0.75em;
    font-weight: 700;
    letter-spacing: 0.06em;
    padding: 0 4px;
    border-radius: 3px;
}
.sankey-ribbon-picker .ribbon-pick-acc { background: #2a3a2c; color: #a6e3a1; }
.sankey-ribbon-picker .ribbon-pick-rej { background: #3a2a2a; color: #f38ba8; }
.sankey-ribbon-picker .ribbon-pick-path {
    color: #cdd6f4;
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 0.95em;
}
.sankey-ribbon-picker .ribbon-pick-count {
    margin-left: auto;
    color: #a6adc8;
    font-size: 0.8em;
}
/* Hover-halo applied to every ribbon segment of the flow whose
   picker row the cursor is on. A bright stroke makes the path
   pop without otherwise altering the Sankey, so the user can
   confirm what they're about to pick before clicking. */
.sankey-segment.ribbon-pick-halo {
    stroke: #ffd166;
    stroke-width: 2;
    stroke-opacity: 0.95;
    paint-order: stroke fill;
}

/* Flow table modal — replaces the per-prefix route table. One row
   per flow; click the row to close the modal and select that flow
   in the Sankey behind it. */
.flow-modal { width: min(96vw, 1100px); max-height: 86vh; }
.flow-modal .flow-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.92em;
}
.flow-modal .flow-table th {
    text-align: left;
    padding: 0.4em 0.6em;
    border-bottom: 1px solid #45475a;
    color: #a6adc8;
    font-weight: 600;
    font-size: 0.85em;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    position: sticky;
    top: 0;
    background: #1e1e2e;
}
.flow-modal .flow-table td {
    padding: 0.4em 0.6em;
    border-bottom: 1px solid #313244;
    vertical-align: top;
}
.flow-modal .flow-row { cursor: pointer; }
.flow-modal .flow-row:hover { background: #313244; }
.flow-swatch {
    display: inline-block;
    width: 14px;
    height: 14px;
    border-radius: 3px;
    vertical-align: middle;
}
.flow-status {
    padding: 1px 8px;
    border-radius: 10px;
    font-size: 0.75em;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 600;
}
.flow-status.accepted { background: #a6e3a133; color: #a6e3a1; }
.flow-status.rejected { background: #f38ba833; color: #f38ba8; }
.flow-table-as {
    color: #89b4fa;
    cursor: pointer;
    text-decoration: none;
    font-family: monospace;
}
.flow-table-as:hover { text-decoration: underline; }
.flow-table-arrow { color: #585b70; margin: 0 0.2em; }
.flow-pfx-line {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3em;
    max-width: 60ch;
}
.flow-pfx-line.flow-pfx-expanded {
    max-width: none;
    max-height: 16em;
    overflow: auto;
    border: 1px solid #45475a;
    border-radius: 3px;
    padding: 0.4em;
}
.flow-pfx-count {
    font-weight: 600;
    color: #cdd6f4;
    margin-right: 0.4em;
}
.flow-prefix-pill {
    background: #313244;
    color: #cdd6f4;
    padding: 1px 6px;
    border-radius: 3px;
    font-family: monospace;
    font-size: 0.85em;
}
.flow-prefix-expand {
    background: transparent;
    border: 1px solid #45475a;
    color: #89b4fa;
    padding: 1px 8px;
    border-radius: 10px;
    cursor: pointer;
    font-size: 0.78em;
}
.flow-prefix-expand:hover {
    background: #313244;
}
.flow-show-btn {
    background: transparent;
    border: 1px solid #45475a;
    color: #89b4fa;
    padding: 2px 10px;
    border-radius: 3px;
    cursor: pointer;
    font-size: 0.85em;
    white-space: nowrap;
}
.flow-show-btn:hover {
    background: #313244;
    color: #cdd6f4;
}
