/* Panel styling that MudBlazor's theme cannot express.
   Anything colour-related here must match Panel/PanelTheme.cs. */

:root {
    --bg: #0F1116;
    --surface: #171A21;
    --surface-raised: #1D212A;
    --line: #252A35;
    --text: #E6E9F0;
    --text-2: #939BAD;
    --text-3: #6B7385;
    --primary: #4C8DFF;
    --success: #34C77B;
    --warning: #E9A13B;
    --error: #E5584C;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

/* --- Inter, served from this origin ---

   Not from Google. Someone signing in from Tehran is on the connection this
   whole product exists to work around, and fonts.googleapis.com is one of the
   names that does not resolve there: the panel would quietly render in a
   system face on precisely the network it was built for. Two subsets, latin
   and latin-ext, variable across the weights the theme uses.

   Inter is SIL Open Font License 1.1 — see fonts/LICENSE.txt. */
@font-face {
    font-family: Inter;
    font-style: normal;
    font-weight: 400 600;
    font-display: swap;
    src: url(fonts/inter-latin.woff2) format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                   U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                   U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: Inter;
    font-style: normal;
    font-weight: 400 600;
    font-display: swap;
    src: url(fonts/inter-latin-ext.woff2) format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
                   U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                   U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* The font stack lives here, not only in the MudBlazor theme.
   The sign-in pages render without MudThemeProvider — they are static SSR by
   design — so anything set only in the theme never reaches them, and they fall
   back to the browser's serif default. */
html, body {
    background: var(--bg);
    color: var(--text);
    font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    -webkit-font-smoothing: antialiased;
}

/* --- status --- */
.status { display: inline-flex; align-items: center; gap: .45rem; font-size: .78rem; white-space: nowrap; }
.status .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.status.online  .dot { background: var(--success); box-shadow: 0 0 0 3px rgba(52,199,123,.15); }
.status.offline .dot { background: var(--text-3); }
.status.pending .dot { background: var(--warning); box-shadow: 0 0 0 3px rgba(233,161,59,.15); }
.status.error   .dot { background: var(--error); box-shadow: 0 0 0 3px rgba(229,88,76,.15); }
.status.online  { color: var(--success); }
.status.pending { color: var(--warning); }
.status.error   { color: var(--error); }
.status.offline { color: var(--text-3); }

/* --- small pill, for roles and report states --- */
.pill { display: inline-block; padding: .1rem .5rem; border-radius: 999px; font-size: .7rem;
        font-weight: 600; letter-spacing: .02em; border: 1px solid var(--line);
        color: var(--text-2); background: var(--surface-raised); white-space: nowrap; }
.pill.relay { color: #7FB3FF; border-color: #29405F; background: #17222F; }
.pill.exit  { color: #6FE3C0; border-color: #1F4A3F; background: #14251F; }
.pill.owner { color: #E9A13B; border-color: #4A3A1C; background: #241D10; }

/* Smart routing. A domain list an operator has not finished configuring, and
   the outcomes a traffic report distinguishes. `unrouted` is amber rather than
   red on purpose: nothing is broken yet, but it will be the moment a subscriber
   is given the list. */
.pill.list      { color: #B79CFF; border-color: #3A2F5C; background: #1C1830; }
.pill.unrouted  { color: #E9A13B; border-color: #4A3A1C; background: #241D10; }
.pill.proxied   { color: #6FE3C0; border-color: #1F4A3F; background: #14251F; }
.pill.forwarded { color: #7FB3FF; border-color: #29405F; background: #17222F; }
.pill.blocked   { color: #F08A80; border-color: #4E2723; background: #281715; }

/* A domain the operator deliberately took off the exit. It shares the blue of
   `forwarded` because it is the same outcome — the name resolves honestly and
   the client connects for itself — and is dashed so that a carve-out inside a
   list of otherwise-carried names is visible without reading each one. */
.pill.direct { color: #7FB3FF; border-color: #29405F; background: #17222F; border-style: dashed; }

/* Routers. The three modes share the vocabulary the rest of the panel already
   uses for "carried" and "direct": a tunnelled mode is green because the traffic
   is going through us, and dns is the same blue as `forwarded` because that is
   literally what it does — the router resolves through the relay and the
   household's connections go out on their own line.

   `ok` / `warn` / `bad` are the generic three, added here because a router's plan
   has an applied state that is not a role and not a routing outcome. They match
   the status-dot colours so that a green pill and a green dot on the same row
   cannot mean different things. */
.pill.dns       { color: #7FB3FF; border-color: #29405F; background: #17222F; }
.pill.wireguard { color: #6FE3C0; border-color: #1F4A3F; background: #14251F; }
.pill.xray      { color: #B79CFF; border-color: #3A2F5C; background: #1C1830; }
.pill.auto      { color: #E9A13B; border-color: #4A3A1C; background: #241D10; }
.pill.none      { color: var(--text-3); }
.pill.ok        { color: #6FE3C0; border-color: #1F4A3F; background: #14251F; }
.pill.warn      { color: #E9A13B; border-color: #4A3A1C; background: #241D10; }
.pill.bad       { color: #F08A80; border-color: #4E2723; background: #281715; }

/* --- fact tables: a label and its value, down the page ---

   Used on every detail page. It had no styling at all, which a browser renders
   as two columns flush against each other: "WAN address164.215.162.187",
   "Memory free335.1 MB", "Since enrolment24.8 GB". Every page in the panel read
   that way, and the denser the page the worse it got.

   Top-aligned rather than middle, because a value can be a pill with two lines
   of explanation under it and a label floating beside its centre reads as
   belonging to neither row. */
table.facts { width: 100%; border-collapse: collapse; }

table.facts > tbody > tr > td,
table.facts > tr > td {
    padding: .28rem 0;
    vertical-align: top;
    line-height: 1.5;
}

/* The label column. Muted and narrow, so the eye runs down the values rather
   than down the labels — the values are what anybody came to read. min-width
   stops a one-word label from collapsing the column and leaving the values
   ragged; max-width stops a long one from squeezing them. */
table.facts > tbody > tr > td:first-child,
table.facts > tr > td:first-child {
    color: var(--text-3);
    padding-right: 1.25rem;
    min-width: 8.5rem;
    max-width: 14rem;
    white-space: normal;
}

/* A single cell spanning both columns is a note, not a label, so it must not
   inherit the muted label colour or the narrow width. */
table.facts > tbody > tr > td[colspan],
table.facts > tr > td[colspan] { color: inherit; min-width: 0; max-width: none; padding-right: 0; }

/* Values that are quantities line up on the right, so magnitudes can be
   compared by eye down a column instead of read one at a time. */
table.facts td .num { display: inline-block; min-width: 5.5rem; text-align: right; }

/* Small facts inside a value cell — a MAC under a hostname, "42 flows" beside a
   size — are separated rather than run together. */
table.facts td > .mono + .muted,
table.facts td > .muted + .mono,
table.facts td > span + span { margin-left: .5rem; }

/* A fact table whose first column is content rather than a label — a
   destination, a device — so it neither takes the muted colour nor the narrow
   width, and the value column is free to sit hard right. */
table.facts.wide > tbody > tr > td:first-child,
table.facts.wide > tr > td:first-child {
    color: inherit;
    min-width: 0;
    max-width: none;
    width: 100%;
    padding-right: .75rem;
}

/* A row of small yes/no facts about one subsystem — the traffic collector's
   three sources and its counts. A flex row rather than a table because the
   items are independent and of very different widths, and because it has to
   wrap on a narrow window instead of forcing the card wider. */
.factstrip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem 1.5rem;
    font-size: .8rem;
    line-height: 1.6;
}

.factstrip > span { display: inline-flex; align-items: center; gap: .35rem; }

/* The four figures at the top of a report. A grid rather than a flex row so the
   columns line up down the page whatever the labels' lengths, and so it folds to
   two-up and then one-up without any breakpoint of its own. */
.summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 1rem 1.5rem; }
.summary > div { display: flex; flex-direction: column; gap: .15rem; }
.summary > div > span:first-child { font-size: .75rem; }
.summary b { font-family: var(--mono); font-size: 1.35rem; font-weight: 500; line-height: 1.2; }
.summary .muted:last-child { font-size: .72rem; }

/* --- time charts on the report ---

   The plot is an SVG stretched to the card's width with
   preserveAspectRatio="none", which would stretch any text drawn inside it too.
   So the axis labels are HTML positioned over and under it, at a real size. */
.timechart { position: relative; padding-right: 5rem; }
.timechart-y {
    position: absolute; top: 0; right: 0; bottom: 0; width: 5rem;
    display: flex; flex-direction: column; justify-content: space-between;
    padding-left: .5rem; font-family: var(--mono); font-size: .68rem; color: var(--text-3);
    white-space: nowrap;
}
.timechart-y > span { line-height: 1; }
.timechart-y > span:last-child { margin-bottom: -.34rem; }

.timechart-x {
    display: flex; justify-content: space-between;
    margin: .35rem 5rem .25rem 0;
    font-family: var(--mono); font-size: .68rem; color: var(--text-3);
}

.timechart-key { display: flex; flex-wrap: wrap; gap: .35rem 1.25rem; font-size: .75rem; }
.timechart-key > span { display: inline-flex; align-items: center; gap: .4rem; }
.timechart-key i { width: .7rem; height: .18rem; border-radius: 1px; display: inline-block; }
.timechart-key b { font-family: var(--mono); font-weight: 500; }

/* --- monospace values: IPs, tokens, ASNs --- */
.mono { font-family: var(--mono); font-size: .8rem; }
.muted { color: var(--text-3); }

/* --- a block meant to be copied out --- */
pre.copyblock { white-space: pre; overflow-x: auto; background: #0B0D12; color: #D6DBE8;
                border: 1px solid var(--line); padding: .8rem .9rem; border-radius: 8px;
                font-family: var(--mono); font-size: .76rem; line-height: 1.5; margin: .6rem 0 0; }

/* --- raw tool output --- */
pre.raw { white-space: pre; overflow-x: auto; background: #0B0D12; color: #C7CDDB;
          border: 1px solid var(--line); padding: .75rem; border-radius: 6px;
          font-family: var(--mono); font-size: .72rem; line-height: 1.45; margin: 0; }

/* --- key/value detail lists --- */
.kv { width: 100%; border-collapse: collapse; }
.kv td { padding: .38rem 0; border-bottom: 1px solid var(--line); vertical-align: top; font-size: .82rem; }
.kv tr:last-child td { border-bottom: 0; }
.kv td:first-child { color: var(--text-3); width: 40%; padding-right: 1rem; }

/* --- login --- */
.auth-shell { min-height: 100vh; display: grid; place-items: center; padding: 2rem; background: var(--bg); }
.auth-card { width: min(400px, 100%); background: var(--surface); border: 1px solid var(--line);
             border-radius: 12px; padding: 2rem; }
.auth-card h1 { margin: 0; font-size: 1.3rem; color: var(--text); letter-spacing: -.01em; }
.auth-card p.sub { margin: .3rem 0 1.6rem; color: var(--text-3); font-size: .82rem; }
.auth-card label { display: block; margin-bottom: .35rem; font-size: .76rem; color: var(--text-2); }
.auth-card input { width: 100%; box-sizing: border-box; padding: .6rem .7rem; margin-bottom: 1.1rem;
                   background: #0E1015; color: var(--text); border: 1px solid #2E3542;
                   border-radius: 7px; font-size: .88rem; font-family: inherit; }
.auth-card input:focus { outline: 2px solid var(--primary); outline-offset: -1px; border-color: transparent; }
.auth-card button { width: 100%; padding: .65rem; background: var(--primary); color: #fff; border: 0;
                    border-radius: 7px; font-size: .88rem; font-weight: 600; cursor: pointer; }
.auth-card button:hover { filter: brightness(1.1); }
.auth-note { margin-top: 1.2rem; font-size: .76rem; color: var(--text-3); }
.auth-note a { color: var(--primary); text-decoration: none; }
.msg { padding: .6rem .7rem; border-radius: 7px; margin-bottom: 1.1rem; font-size: .8rem; }
.msg.err { background: #2A1518; border: 1px solid #5A2A2E; color: #F3A9A2; }
.msg.ok  { background: #12261C; border: 1px solid #245239; color: #9BE3BC; }

/* --- Blazor's reconnect overlay --- */
.reconnect-modal { display: none; }
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: grid; place-items: center; position: fixed; inset: 0; z-index: 3000;
    background: rgba(9,10,14,.8); backdrop-filter: blur(2px);
}
.reconnect-card { background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
                  padding: 1.5rem 1.75rem; text-align: center; min-width: 260px; }
.reconnect-title { margin: 0 0 .3rem; font-weight: 600; color: var(--text); }
.reconnect-body { margin: 0; font-size: .82rem; color: var(--text-2); }
.reconnect-reload { display: none; margin-top: .9rem; color: var(--primary); font-size: .82rem; }
#components-reconnect-modal.components-reconnect-failed .reconnect-reload,
#components-reconnect-modal.components-reconnect-rejected .reconnect-reload { display: inline-block; }


/* --- copy button riding on a copyable block --- */
.copywrap { position: relative; }
.copywrap .copyblock { padding-right: 7rem; }
.copywrap-btn { position: absolute; top: .95rem; right: .45rem; }

/* --- table toolbars ---
   The toolbar is a header for the table, not a second card. Keeping it on the
   same ground and separating it with the same line the rows use stops it
   reading as a control panel floating above unrelated data. */
.mud-table-toolbar { border-bottom: 1px solid var(--line); min-height: 56px; gap: .75rem;
                     padding-left: 16px; padding-right: 16px; }
.table-filter { flex: none; }
.table-filter .mud-toggle-item { font-size: .74rem; padding: .1rem .7rem; }

/* On a phone the search box and the filter cannot share a row: the filter
   loses, and its labels get sliced down the middle. Let the toolbar wrap and
   give the search the full width. */
@media (max-width: 700px) {
    /* height, not just min-height: MudToolBar pins a fixed height, so a
       wrapped second row spills over the table header below it. */
    .mud-table-toolbar { flex-wrap: wrap; height: auto; min-height: 0;
                         padding-top: .6rem; padding-bottom: .6rem; }
    .mud-table-toolbar > .mud-input-control { max-width: none !important; width: 100%; }
}

/* Sort arrows are invisible until hovered by default, which hides the fact
   that a column is sortable at all. */
.mud-table-sort-label:hover { color: var(--text); }

/* --- dialogs ---
   A dialog that shares the page's surface colour disappears into it. This one
   is one step lighter with a visible edge, so the modal reads as a thing in
   front of the page rather than a region of it. */
.mud-dialog { background: var(--surface-raised) !important; border: 1px solid var(--line); }
.mud-dialog-title { padding-bottom: .5rem; }
.mud-dialog-content { padding-top: .5rem; }
.mud-dialog-actions { border-top: 1px solid var(--line); padding: .75rem 1.5rem; gap: .5rem; }

/* Domain chips inside the list editor. The remove button sits inside the chip
   rather than beside it so the hit target is obvious and the row does not
   reflow when one is taken away. */
.domain-chips { display: flex; flex-wrap: wrap; gap: .3rem; max-height: 12rem; overflow-y: auto; }
.domain-chip { display: inline-flex; align-items: center; gap: .15rem; padding-right: .1rem; }
.domain-chip .domain-chip-x { width: 1.1rem; height: 1.1rem; padding: 0; opacity: .45; }
.domain-chip .domain-chip-x:hover { opacity: 1; color: var(--error); }
.domain-chip .domain-chip-x .mud-icon-root { font-size: .8rem; }

/* The framework file picker, made to sit beside MudBlazor's controls without
   looking like the browser default dropped into the page. */
.file-input { font-size: .8rem; color: var(--text-2); }
.file-input::file-selector-button {
  background: var(--surface-raised); color: var(--text); border: 1px solid var(--line);
  border-radius: 6px; padding: .3rem .7rem; margin-right: .6rem; cursor: pointer;
  font-size: .8rem; font-family: inherit;
}
.file-input::file-selector-button:hover { border-color: var(--primary); color: var(--primary); }

/* --- sparkline ---
   Stretched to the card's width with preserveAspectRatio="none", so the strokes
   inside it are drawn with vector-effect:non-scaling-stroke — without that a
   wide card renders a chart with a hairline top and fat sides. */
.spark { width: 100%; display: block; }
.spark-key { display: inline-flex; align-items: center; gap: .35rem; color: var(--text-3); }
.spark-key i { width: 12px; height: 2px; border-radius: 1px; display: inline-block; }
.spark-key i.dashed { height: 0; border-top: 2px dashed currentColor; background: none !important; }

/* --- the overview's attention list ---
   Rows rather than an alert stack: they are a queue of things to go and do, and
   each one is a link to the page that does it. */
.attention { display: flex; align-items: center; gap: .6rem; padding: .5rem .25rem;
             border-bottom: 1px solid var(--line); }
.attention:last-child { border-bottom: 0; }
.attention:hover { background: var(--surface-raised); border-radius: 6px; }

/* --- vertical rhythm between the blocks that make up a page ---------------
   
   One rule, because the problem it fixes was every page inventing its own
   answer. A page is a stack of blocks — a header, rows of stat cards, panels,
   a table — and the gap between them was whatever `mb-1`, `mb-3` or `mb-5`
   somebody reached for while writing that page. Read one page and it looks
   deliberate; read three and the rhythm changes under you.
   
   The gap is expressed here rather than as a class, so a new block gets it by
   existing rather than by remembering. --spacing is MudBlazor's own unit, so
   the space BETWEEN blocks matches the space between cards WITHIN a row, which
   is the comparison an eye actually makes.
   
   MudGrid carries a negative margin of half its spacing on every side — it is
   how the gutter is made — so a grid's visual top edge sits above its box. The
   two rules below add that half back, which is why a grid and a paper end up
   looking equally far apart rather than the grid looking squashed. */
.page-body > * + * { margin-top: 24px; }
.page-body > .mud-grid + * { margin-top: 12px; }
.page-body > * + .mud-grid { margin-top: 12px; }
.page-body > .mud-grid + .mud-grid { margin-top: 0; }

/* The header owns the space under itself and nothing else adds to it. */
.page-body > .page-header { margin-bottom: 0; }

/* Attention items across the full width, rather than stacked in a side column.
   
   Two or three at a time is the normal case and a single column of them wastes
   the width while pushing everything else down the page. The border that
   separated stacked rows is dropped here — in columns it reads as a table nobody
   asked for. */
.attention-grid { display: grid; gap: .25rem 1.5rem;
                  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.attention-grid .attention { border-bottom: 0; }
