/* Background: single SVG from ./static/backround-main.svg (byte-identical to
   assets/backround-main_compressed.svg). The picture is painted by a fixed
   body::before layer at opacity .4 so the dashboard colours don't overwhelm
   the content, while the preloader dims its own copy from 1.0 → .4. */
:root{--bg-url:url('./static/backround-main.svg')}
*{box-sizing:border-box;margin:0;padding:0}
html{height:100%}
body{display:flex;flex-direction:column;min-height:100%;background:#0a0a0b;color:#d4d4d8;font-family:-apple-system,system-ui,'Segoe UI',sans-serif;font-size:15px;-webkit-font-smoothing:antialiased;padding-bottom:env(safe-area-inset-bottom,0);-webkit-user-select:none;user-select:none}
body::before{content:'';position:fixed;inset:0;z-index:-1;background:var(--bg-url) center/cover no-repeat;opacity:.4;pointer-events:none}

/* z-index:3 keeps the label above `.tab-slider` (z-index:2) so the
   active tab's text doesn't disappear behind the glass pill. */
/* Vertical padding is sized so the button fills the full nav-bar height:
 * `nav` has no padding of its own, so `.tab-btn` padding IS the bar. A
 * generous vertical padding means a finger can tap anywhere in the bar's
 * strip and still land on the correct tab (see annotated screenshot). */
.tab-btn{position:relative;z-index:3;padding:14px 14px;font-size:15px;font-weight:500;border:none;border-bottom:2px solid transparent;color:#71717a;cursor:pointer;white-space:nowrap;background:none;transition:color .25s ease}
.tab-btn:hover{color:#a1a1aa}
.tab-btn.tab-active{color:#fff}

/* Tab bar slider — full-height glass pill that slides behind the active
   `.tab-btn` inside a `.tab-track` (mirrors `.chip-slider`: same
   backdrop-filter + gradient recipe). Tabs and regions swapped their
   slider styles when the two rows swapped place in the header.

   The active pill has to visually overpaint BOTH grey divider lines
   that bracket the nav row — `header>.wrap::after` above and
   `nav>.wrap::after` below — so its top and bottom edges blend into
   the borders instead of stopping 1px short of them. The bottom edge
   is handled by `.tab-slider { z-index:2 }` (lifts it above the
   nav-local ::after). For the top edge we pull the whole `nav` up by
   1px with `margin-top:-1px` and give it `position:relative; z-index:1`
   so nav and its descendants stack above `header` in the body-level
   stacking context. The slider still uses `top:0; height:100%`, but
   because nav now sits 1px into header's bottom, that top pixel lands
   exactly on the header's grey divider and repaints it. We can't
   achieve the same by setting `top:-1px` on the slider itself —
   `tab-track` is `overflow-x:auto`, which per spec forces overflow-y
   to clip, so a slider sticking out of the track gets cropped. */
nav{position:relative;z-index:1;margin-top:-1px}
.tab-track{position:relative}
/* `transform:translate3d(0,0,0)` seeds a concrete "from" value so Safari
 * can interpolate when the first `translate3d(Xpx,0,0)` is written from
 * JS. Without it WebKit sees `none → translate3d(...)` and skips the
 * transition (user-visible as a teleport of the slider bar). The 3d
 * variant also pins the element to its own compositor layer, which
 * sidesteps another Safari bug where transitions inside a
 * `-webkit-overflow-scrolling:touch` scroll container render as jumps.
 * `will-change:transform` only — `width` is a layout prop, advertising
 * it just confuses Safari's layer promotion without helping perf on a
 * 2px bar. */
.tab-slider{position:absolute;top:0;left:0;width:0;height:100%;background:linear-gradient(135deg,rgba(197,15,53,.78),rgba(105,11,31,.72));box-shadow:0 8px 24px -8px rgba(197,15,53,.55),inset 0 1px 0 rgba(255,255,255,.12);backdrop-filter:blur(14px) saturate(160%);-webkit-backdrop-filter:blur(14px) saturate(160%);opacity:0;pointer-events:none;z-index:2;transform:translate3d(0,0,0);transition:transform .38s cubic-bezier(.32,.72,0,1),width .38s cubic-bezier(.32,.72,0,1),opacity .25s ease;will-change:transform}
.tab-slider::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,rgba(255,255,255,.14),rgba(255,255,255,0) 50%,rgba(0,0,0,.12));pointer-events:none}
.tab-slider.no-anim{transition:none}

table{border-collapse:collapse;width:100%}
th{text-align:left;padding:11px 10px;font-size:12px;line-height:1.25;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#71717a;border-bottom:1px solid #1f181b;white-space:nowrap}
td{padding:10px 10px;border-bottom:1px solid #161012;font-size:14px;line-height:1.3;white-space:nowrap}
th.c,td.c{text-align:center}
/* Zebra striping. Two mechanisms coexist:
 *   - automatic `tr:nth-child(even)` for simple tables (proxy list,
 *     expanded server tables, admin nodes/profiles) where every tr is
 *     visible and the parity is stable;
 *   - explicit `tr.row-alt` class for tables with hidden sibling rows
 *     (e.g. VPN main table where expandable groups get a paired hidden
 *     `.expand-row`). The render code assigns `.row-alt` based on the
 *     index among VISIBLE rows so the stripes stay aligned regardless
 *     of which groups are expandable.
 * Tables marked `.tbl-vpn` opt out of the nth-child rule entirely. */
table:not(.tbl-vpn) tbody tr:nth-child(even) td{background:rgba(255,230,230,.018)}
tbody tr.row-alt td{background:rgba(255,230,230,.018)}
td.sec-name,th.sec-name{background:rgba(255,230,230,.029)}
td.sec-meta,th.sec-meta{background:rgba(255,230,230,.029)}
table:not(.tbl-vpn) tbody tr:nth-child(even) td.sec-name,table:not(.tbl-vpn) tbody tr:nth-child(even) td.sec-meta{background:rgba(255,230,230,.047)}
tbody tr.row-alt td.sec-name,tbody tr.row-alt td.sec-meta{background:rgba(255,230,230,.047)}
/* Row hover. Base tone is neutral grey; rows carrying an `edge-*`
 * status class swap in a green / orange / red wash so the hover reads
 * the row's signal state at a glance (working / partial / failed).
 *
 * Why the verbose selector list: the zebra rules above assign
 * higher-specificity backgrounds to `td.sec-name` / `td.sec-meta` on
 * `nth-child(even)` / `.row-alt` rows (specificity 0,3,4 / 0,2,3). A
 * plain `tr:hover td` (0,1,2) loses those fights, and the hover ended
 * up patchy — side columns kept their zebra tint while the middle
 * flipped to the hover colour. We match each zebra scope cell by cell
 * so every cell in the hovered row lands on `var(--hover-bg)`. */
tr{--hover-bg:#08070a}
tr.edge-ok{--hover-bg:rgba(34,197,94,.05)}
tr.edge-half{--hover-bg:rgba(255,88,10,.05)}
tr.edge-fail{--hover-bg:rgba(239,68,68,.05)}
tbody tr:hover td,
tbody tr:hover td.sec-name,
tbody tr:hover td.sec-meta,
table:not(.tbl-vpn) tbody tr:nth-child(even):hover td,
table:not(.tbl-vpn) tbody tr:nth-child(even):hover td.sec-name,
table:not(.tbl-vpn) tbody tr:nth-child(even):hover td.sec-meta,
tbody tr.row-alt:hover td,
tbody tr.row-alt:hover td.sec-name,
tbody tr.row-alt:hover td.sec-meta{background:var(--hover-bg)}

.st{font-weight:700;font-size:15px}
.st-ok{color:#22c55e}
.st-warn{color:#eab308}
.st-fail{color:#ef4444}
.st-none{color:#3f3f46;display:inline-block;min-width:17px;line-height:17px;text-align:center;vertical-align:middle;font-weight:400}

.dot{width:9px;height:9px;border-radius:50%;display:inline-block;flex-shrink:0}
.dot-g{background:#22c55e}.dot-y{background:#eab308}.dot-r{background:#ef4444}.dot-0{background:#27272a}

/* Colored left edge on VPN rows. Replaces the per-row score dot with a
 * 1px-wide vertical gradient stripe that spans the full row height and
 * sits flush with the table wrapper's visual edge (the VPN wrapper drops
 * its left border/radius so the stripe itself forms the edge). Gradients
 * mirror assets/yes.svg, assets/half.svg and assets/no.svg. */
/* VPN table drops its left border (the coloured edge stripe per row forms
   the visible left edge) and moves its bottom border from the real
   border slot into an `inset` box-shadow one pixel inside the
   padding-box. Why: the real border sits outside `.tbl`'s padding-box,
   and `.tbl`'s `overflow-x:auto` clips descendants at the padding-box
   — so the tfoot slider could never physically sit on top of the real
   border. Relocating the line inside the clip lets the slider (also
   inside padding-box) land directly on top of it.
   `padding-bottom:1px` reserves that strip so the `tfoot td`'s own
   background (`#040304`) stops before the shadow instead of painting
   over it — inset shadows render above element background but under
   descendants, so without the padding the td would mask the line.
   Height budget stays identical: the dropped `border-bottom:1px` is
   compensated by the new `padding-bottom:1px`. */
.tbl:has(> .tbl-vpn){border-left:0;border-bottom:0;padding-bottom:1px;box-shadow:inset 0 -1px 0 #1c1619}
/* Footer row hosts the provider chips as a single full-width band that
 * seals the bottom of the VPN table. No left stripe, uniform background,
 * and it must not react to row hover.
 *
 * Layout notes:
 * - The footer row uses TWO tds: the first one carries the "Провайдер:"
 *   label and inherits `.sec-name`'s `width:1%` so it shrinks to the
 *   same width as the name column in tbody; the second one (`.prov-cell`)
 *   spans every remaining column (`colspan = 3 + targets.length`) so the
 *   chip track starts exactly at the boundary between the name column
 *   and the "scores" columns. No pixel magic needed — the browser's
 *   column sizing keeps label and chips aligned with the rows above
 *   regardless of how long the VPN names get.
 * - `padding-bottom:0` on the chips cell drops the slider flush against
 *   the table's inner bottom edge, so it reads as the table's bottom
 *   line lighting up under the active provider (see the upward-glow
 *   `box-shadow` on `.chip-track-prov > .chip-slider` further below).
 * - `border-top` used to be `2px solid #201719` which read as a bold
 *   seam between the last row and the chips; dimmed to a single-pixel
 *   hairline matching the rest of the table's row dividers. */
.tbl-vpn tfoot td{background:#040304;padding:8px 10px;border-top:1px solid #161012;border-bottom:0;white-space:normal;vertical-align:middle}
.tbl-vpn tfoot tr:hover td{background:#040304}
/* Label cell sits inside the `.sec-name` column. The "Провайдер:" span
   now carries its own leading `ico-sm` icon, so the cell's padding only
   needs to match tbody's base left padding — the icon + gap recipe
   inside the span lines things up with the provider rows for free
   (10px desktop, 7px mobile, mirroring `.tbl-vpn td.sec-name`).
   `padding-bottom:0` mirrors the chips cell so both cells share the
   same effective content area height; otherwise the label would sit
   4px higher than the chip track's vertical centre because `prov-cell`
   uses `padding-bottom:0` (slider hugs the table's bottom border). */
.tbl-vpn tfoot td.sec-name{padding:8px 10px 0 10px}
/* Chips cell: kill the bottom padding so the slider at the track's
   bottom lands flush with the table's inner bottom edge, and drop the
   left padding so chips start exactly at the scores-column boundary. */
.tbl-vpn tfoot td.prov-cell{padding:8px 10px 0 0}
.tbl-vpn tfoot .chip-track-prov{padding:0}
/* VPN column sizing: shrink the name + meta columns to their content so
 * the target-icon columns get to breathe horizontally instead of
 * hugging each other in the middle. Proxy table uses the same three-
 * section layout (name | provider scores | last-check) so it shares
 * the rule. */
.tbl-vpn th.sec-name,.tbl-vpn td.sec-name,
.tbl-vpn th.sec-meta,.tbl-vpn td.sec-meta{width:1%}
/* Proxy table: lock the whole table geometry so switching operator filters
 * (which changes which proxy rows are visible) can't reflow the columns.
 * `table-layout:fixed` freezes column widths based on the explicit values
 * below; the name column is pinned to match `.proxy-name-clip`'s 21ch cap,
 * the clock column gets a fixed numeric-safe slot, and provider score
 * columns auto-distribute the remainder evenly. */
.tbl-proxy{table-layout:fixed}
.tbl-proxy th.sec-name,.tbl-proxy td.sec-name{width:22ch}
.tbl-proxy th.sec-meta,.tbl-proxy td.sec-meta{width:56px}
/* VPN expanded server table: same fixed-layout approach as proxy table.
 * Name column capped at 22ch (matches proxy-name-clip), ping at 56px,
 * target icons auto-distribute the rest evenly. */
.tbl-srv{table-layout:fixed}
.tbl-srv th:first-child,.tbl-srv td:first-child{width:22ch}
.tbl-srv th:nth-child(2),.tbl-srv td:nth-child(2){width:56px}
/* Proxy name clipper: hard-cap the visible name at 21ch and fade the tail
 * from 15ch to 21ch so long IPs/domains don't bloat the name column. Uses
 * `ch` (width of the "0" glyph) which is monospace-ish enough for numeric
 * IPs and short domains. The mask extends past the element, so names
 * shorter than 15ch render fully opaque. Element must be inline-block for
 * max-width + overflow to take effect inside the `<td>`. */
.proxy-name-clip{display:inline-block;max-width:21ch;overflow:hidden;white-space:nowrap;vertical-align:bottom;-webkit-mask-image:linear-gradient(to right,#000 0,#000 15ch,transparent 21ch);mask-image:linear-gradient(to right,#000 0,#000 15ch,transparent 21ch)}
td.sec-name{position:relative}
td.sec-name.edge-ok::before,
td.sec-name.edge-half::before,
td.sec-name.edge-fail::before{content:'';position:absolute;left:0;top:0;bottom:0;width:1px;pointer-events:none}
td.sec-name.edge-ok::before{background:linear-gradient(180deg,#33783E,#07593E)}
td.sec-name.edge-half::before{background:linear-gradient(180deg,#DE3513,#FF580A)}
td.sec-name.edge-fail::before{background:linear-gradient(180deg,#6E0C20,#C10F34)}

.badge{padding:1px 6px;border-radius:9999px;font-size:13px;font-weight:500;white-space:nowrap}
.badge-ok{background:#052e16;color:#22c55e}
.badge-fail{background:#450a0a;color:#ef4444}
.badge-info{background:#172554;color:#60a5fa}

.wrap{width:100%;max-width:860px;margin:0 auto}

/* Divider lines under header / nav (tabs) / region chips.
 *
 * Constraints:
 *   1. The line must end exactly where the table inside <main class="wrap">
 *      starts, i.e. inset by main's horizontal padding (16px desktop,
 *      12px mobile) so it never protrudes past the table's visible edge.
 *   2. Vertically the line must sit at the BOTTOM of the wrap element
 *      (= top of the next section), not at the bottom of the content-box,
 *      so the wrap's padding-bottom stays as breathing room *above* the
 *      line rather than below it.
 *
 * A `border-bottom` fails (1): it spans the full border-box width.
 * A `background-image` clipped to content-box fails (2): the line would
 *   float `padding-bottom` pixels above the element's real bottom edge.
 * A pseudo-element absolutely positioned to the padding-box bottom with
 *   explicit left/right inset satisfies both constraints and needs no
 *   extra DOM. */
header>.wrap,
nav>.wrap,
.region-chips>.wrap{position:relative}
header>.wrap::after,
nav>.wrap::after,
.region-chips>.wrap::after{
  content:'';
  position:absolute;
  left:16px;
  right:16px;
  bottom:0;
  height:1px;
  background:#1e1e24;
  pointer-events:none;
}
.tbl{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;border-radius:8px;border:1px solid #1c1619;background:#090708}
.tbl::-webkit-scrollbar{display:none}

.fchip{display:inline-flex;align-items:center;padding:3px 8px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid #23232a;color:#71717a;background:transparent;transition:color .18s ease,border-color .18s ease;user-select:none;text-transform:uppercase;letter-spacing:.03em}
.fchip:hover{border-color:#71717a;color:#a1a1aa}
/* Multi-toggle proxy filter states keep their signal colours. */
.fchip.f-ok{background:rgba(34,197,94,.08);border-color:rgba(34,197,94,.4);color:#22c55e}
.fchip.f-fail{background:rgba(239,68,68,.08);border-color:rgba(239,68,68,.4);color:#ef4444}

/* Region + single-select provider chips: borderless text labels. The
 * glass-pill slider provides the only button-like affordance — idle
 * chips must not look like clickable buttons on their own. */
.region-chip{display:inline-flex;align-items:center;padding:12px 12px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;border:1px solid transparent;color:#71717a;background:transparent;transition:color .18s ease;user-select:none;white-space:nowrap}
.region-chip:hover{color:#a1a1aa}
.region-chip-active{color:#fff}
.region-chip-active:hover{color:#fff}

/* ── Chip slider (full-bar-height rectangle that slides between chips) ──
 * `.chip-slider` is positioned absolutely inside each `.chip-track` and
 * spans the full vertical height of the track. Only the horizontal
 * transform + width animate between chips. No border-radius — this is a
 * tab-style indicator, not a button sitting on top of a button.
 *
 * NB: NO `isolation:isolate` here. The slider's backdrop-filter has to be
 * able to see the page background (body bg image) underneath, otherwise
 * the glass effect collapses into a solid red rectangle.
 */
.chip-track{position:relative;display:inline-flex;align-items:center;gap:2px}
/* Hit-area: the chips absorb the track's former vertical padding so the
 * tap target equals the full bar height. Track padding moves to
 * `.region-chip` (4+8=12). Slider still anchors to the track's bottom
 * edge, which now coincides with the chip's bottom edge — distance from
 * text baseline to the 2 px underline stays identical to the previous
 * layout (12 px). */
.chip-track-bar{padding:0}
/* Fixed-height invisible bar for provider chips so the underline slider
 * doesn't jitter when chips have different natural heights (icon-only
 * vs text). Same shape as `.chip-track-bar` so the underline lands in
 * the same spot on the region row and the provider row.
 *
 * Provider chips are icon-only tappable targets, so they're a touch
 * larger than the generic `.fchip` default — both the hit area (padding)
 * and the glyph (`.ico-sm` override) grow modestly, with the track's
 * `min-height` following so the slider still anchors to a stable
 * baseline. */
/* Same hit-area trick as `.chip-track-bar`: strip vertical padding from
 * the track and push it onto the chip buttons so fingers register on
 * the full provider-bar strip (4+6=10). */
.chip-track-prov{min-height:30px;align-items:center;padding:0;gap:4px}
.prov-sep{width:1px;align-self:center;background:#27272a;flex-shrink:0;margin:0 4px;height:14px}
.chip-track-prov>button{display:inline-flex;align-items:center}
.chip-track-prov>button.fchip{padding:10px 10px;border-radius:9px;transition:filter .3s cubic-bezier(.32,.72,0,1),color .18s ease,border-color .18s ease}
.chip-track-prov>button.fchip .ico-sm{width:18px;height:18px}
/* Inactive provider chips fade back: saturation drops + slight
   brightness cut so the active chip reads as the "lit" selection.
   Filter is cheap to animate and plays nicely with the slider's
   position transition when the user picks a different provider. */
.chip-track-prov>button.fchip:not(.f-ok){filter:saturate(.55) brightness(.82)}
/* Chip labels sit at z-index:3 so they render ABOVE their slider
   (region slider z-index:2, VPN footer slider z-index:0) — the glow
   then pools behind the chip content instead of tinting it from
   the front. Mirrors `.tab-btn`'s z-index:3 vs `.tab-slider` z-index:2
   relationship in the nav bar. Background/border are forced
   transparent so the slider (and its glow) visibly show through. */
.chip-track>button{position:relative;z-index:3;background:transparent!important;border-color:transparent!important}
.chip-track.chip-track-prov>button.fchip.f-ok{color:#fff}
/* Same Safari-friendly seeding as `.tab-slider` — see note above for
 * why translate3d seed + `will-change:transform` (only) are required
 * to get a smooth region/provider slide in WebKit. */
.chip-slider{position:absolute;top:0;left:0;width:0;height:100%;background:linear-gradient(135deg,rgba(197,15,53,.78),rgba(105,11,31,.72));box-shadow:0 8px 24px -8px rgba(197,15,53,.55),inset 0 1px 0 rgba(255,255,255,.12);backdrop-filter:blur(14px) saturate(160%);-webkit-backdrop-filter:blur(14px) saturate(160%);opacity:0;pointer-events:none;z-index:0;transform:translate3d(0,0,0);transition:transform .38s cubic-bezier(.32,.72,0,1),width .38s cubic-bezier(.32,.72,0,1),opacity .25s ease;will-change:transform}
.chip-slider::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,rgba(255,255,255,.14),rgba(255,255,255,0) 50%,rgba(0,0,0,.12));pointer-events:none}
.chip-slider.no-anim{transition:none}

/* Region row AND provider rows (VPN + Proxy) use the underline style
   (2px bottom line). The default `.chip-slider` recipe above is
   currently unused, but kept in case we reintroduce a pill-style track.

   The region row's track sits flush against the grey 1px divider line
   drawn by `.region-chips>.wrap::after`. Both elements are positioned
   descendants of the same `.wrap` with z-index 0 (slider) and auto
   (::after); since ::after comes later in DOM order it was painting
   ON TOP of the slider and masking its bottom pixel, so the red
   underline looked 1px tall instead of 2px.

   We keep the slider at `bottom:0; height:2px` (so it's aligned with
   the divider's bottom edge) and bump its `z-index` to 2 — one pixel
   of the red line now floats above the grey divider and the other
   pixel repaints on top of the grey divider, covering it exactly
   under the active chip. `.chip-track>button` then sits at z-index:3
   so chip labels (and icons) stay above the slider and its glow. */
.chip-track-bar>.chip-slider,
.chip-track-prov>.chip-slider{top:auto;bottom:0;height:2px;background:linear-gradient(135deg,#C50F35,#690B1F);box-shadow:0 0 10px -2px rgba(197,15,53,.55);backdrop-filter:none;-webkit-backdrop-filter:none;border-radius:2px 2px 0 0;z-index:2}
/* Region slider borrows the VPN footer's upward multi-layer light wash.
   Kept at z-index:2 (unlike the VPN variant) because it still has to
   cover the grey `region-chips>.wrap::after` divider under the active
   chip — dropping to z-index:0 would let that divider paint on top
   (::after has z-index:auto, later in DOM) and leave a visible gap in
   the red line. Region chips are text-only, so having the glow render
   in front of them just reads as a "lit" selection rather than an
   obscuring tint. */
.chip-track-bar>.chip-slider{box-shadow:0 -2px 8px rgba(197,15,53,.8),0 -8px 20px rgba(197,15,53,.65),0 -18px 28px rgba(197,15,53,.55),0 -30px 44px rgba(197,15,53,.45)}
/* Provider chip tracks (proxy header + VPN footer) share the same upward
 * multi-layer wash as region chips — reads as a halo of light blooming
 * upward from the active provider instead of a flat 2px bar. The VPN
 * footer variant below still overrides position (`bottom:-1px`) and
 * swaps the shadow to the status-tinted edge-* gradients. */
.chip-track-prov>.chip-slider{box-shadow:0 -2px 8px rgba(197,15,53,.8),0 -8px 20px rgba(197,15,53,.65),0 -18px 28px rgba(197,15,53,.55),0 -30px 44px rgba(197,15,53,.45)}
.chip-track-bar>.chip-slider::after,
.chip-track-prov>.chip-slider::after{display:none}
/* Inside the VPN footer the slider sits directly on the table's bottom
   line. `.tbl` paints that line as an `inset` box-shadow one pixel
   above its padding-box bottom (see `.tbl:has(> .tbl-vpn)` above).
   The track ends at the `tfoot td`'s content-box bottom, which lines
   up one pixel above the shadow (because `.tbl` has
   `padding-bottom:1px`), so `bottom:-1px` on the slider drops its
   lower pixel onto the shadow exactly — under the active chip the
   coloured bar replaces the grey line, and the grey line is visible
   everywhere else.
   Upward-biased multi-layer shadow reads as a bright wash of light
   from below. Colours come from the active provider's status class
   (edge-ok / edge-half / edge-fail) set on the track itself, so the
   slider matches the provider's signal colour. */
/* Slider in the VPN footer sits BELOW the chips on the z-axis (z-index:0
   vs the chip buttons' z-index:1) so its upward glow pools behind the
   provider icons rather than tinting them from the front. The slider
   bar itself is still visible because it occupies the bottom strip of
   the track where the chips don't overlap vertically.
   Shadow is a soft, evenly-spaced three-layer wash — no hot spot right
   above the bar — so the light reads as a diffuse halo, not a flame. */
.tbl-vpn tfoot .chip-track-prov>.chip-slider{bottom:-1px;z-index:0;box-shadow:0 -2px 8px rgba(197,15,53,.8),0 -8px 20px rgba(197,15,53,.65),0 -18px 28px rgba(197,15,53,.55),0 -30px 44px rgba(197,15,53,.45)}
.tbl-vpn tfoot .chip-track-prov.edge-ok>.chip-slider{background:linear-gradient(135deg,#33783E,#07593E);box-shadow:0 -2px 8px rgba(34,197,94,.8),0 -8px 20px rgba(34,197,94,.65),0 -18px 28px rgba(34,197,94,.55),0 -30px 44px rgba(34,197,94,.45)}
.tbl-vpn tfoot .chip-track-prov.edge-half>.chip-slider{background:linear-gradient(135deg,#DE3513,#FF580A);box-shadow:0 -2px 8px rgba(255,88,10,.8),0 -8px 20px rgba(255,88,10,.65),0 -18px 28px rgba(255,88,10,.55),0 -30px 44px rgba(255,88,10,.45)}
.tbl-vpn tfoot .chip-track-prov.edge-fail>.chip-slider{background:linear-gradient(135deg,#C10F34,#6E0C20);box-shadow:0 -2px 8px rgba(193,15,52,.8),0 -8px 20px rgba(193,15,52,.65),0 -18px 28px rgba(193,15,52,.55),0 -30px 44px rgba(193,15,52,.45)}
/* Brand-coloured provider sliders */
.chip-track-prov[data-brand="beeline"]>.chip-slider{background:linear-gradient(135deg,#FCB533,#C89020)!important;box-shadow:0 -2px 8px rgba(252,181,51,.8),0 -8px 20px rgba(252,181,51,.65),0 -18px 28px rgba(252,181,51,.55),0 -30px 44px rgba(252,181,51,.45)!important}
.chip-track-prov[data-brand="mts"]>.chip-slider{background:linear-gradient(135deg,#FF0000,#CC0000)!important;box-shadow:0 -2px 8px rgba(255,0,0,.8),0 -8px 20px rgba(255,0,0,.65),0 -18px 28px rgba(255,0,0,.55),0 -30px 44px rgba(255,0,0,.45)!important}
.chip-track-prov[data-brand="megafon"]>.chip-slider{background:linear-gradient(135deg,#00985F,#006B42)!important;box-shadow:0 -2px 8px rgba(0,152,95,.8),0 -8px 20px rgba(0,152,95,.65),0 -18px 28px rgba(0,152,95,.55),0 -30px 44px rgba(0,152,95,.45)!important}
.chip-track-prov[data-brand="rostelecom"]>.chip-slider{background:linear-gradient(135deg,#EC5D2F,#B8421E)!important;box-shadow:0 -2px 8px rgba(236,93,47,.8),0 -8px 20px rgba(236,93,47,.65),0 -18px 28px rgba(236,93,47,.55),0 -30px 44px rgba(236,93,47,.45)!important}

.legend{display:inline-flex;flex-wrap:wrap;gap:10px;font-size:13px;color:#71717a}
.legend>span{display:inline-flex;align-items:center;gap:3px}
/* Tint the legend's check-icon (a white-filled SVG used as <img>) down to
 * the same neutral grey as the surrounding text — without touching the
 * source SVG. `brightness(.45)` on pure white ≈ #737373, visually a hair
 * off but indistinguishable from `color:#71717a`. */
.legend .ico-sm{filter:brightness(.45)}

.vpn-row{cursor:pointer;transition:background .1s}
/* Hover fill for VPN rows lives in the unified `tr:hover td` block above
 * so the colour can flow from the row's `edge-*` status class via
 * `--hover-bg`. A local `.vpn-row:hover td{background:#0c0b0c}` override
 * used to sit here but it pinned every VPN row to grey on hover,
 * defeating the tint. */
.expand-row td{background:#060405!important;padding:12px}

.ico{width:17px;height:17px;object-fit:contain;vertical-align:middle;border-radius:3px}
th.c .ico,td.c .ico{display:block;margin:0 auto}
.ico-sm{width:14px;height:14px}
th .ico{opacity:.85}

@media(max-width:640px){
  body{font-size:14px}
  .wrap{max-width:100%}
  th{padding:8px 4px;font-size:11px}
  td{padding:8px 4px;font-size:12px}
  /* Keep a 2-3px gap between the colored stripe and the VPN name / header
   * text on mobile so the content doesn't hug the edge. */
  .tbl-vpn td.sec-name,.tbl-vpn th.sec-name{padding-left:7px}
  /* Mirror desktop but with tighter gutters and the mobile offset for
   * the label (24px = 7 sec-name pad + 12 ico-sm + 5 gap). */
  .tbl-vpn tfoot td{padding:8px 4px}
  .tbl-vpn tfoot td.sec-name{padding:8px 4px 0 7px}
  .tbl-vpn tfoot td.prov-cell{padding:8px 4px 0 0}
  .tab-btn{padding:12px 10px;font-size:14px}
  /* All top-bar wrappers share the same horizontal gutter as <main> so the
   * divider lines end exactly where the table starts. !important is needed
   * to beat inline `padding:0 16px` on nav/region-chips and
   * `padding:14px 16px 10px` on the header. */
  main{padding:8px 12px!important}
  header .wrap{padding:10px 12px 8px!important}
  nav .wrap,.region-chips .wrap{padding-left:12px!important;padding-right:12px!important}
  /* Match the narrower mobile gutter so the divider line still ends
   * where the table starts. */
  header>.wrap::after,
  nav>.wrap::after,
  .region-chips>.wrap::after{left:12px;right:12px}
  .expand-row td{padding:8px}
  /* Keep the mobile hit-area trick alive: bump chip vertical padding
   * so tap zone fills the full bar (track padding is 0 on both sides). */
  .fchip{padding:2px 6px;font-size:13px}
  .chip-track-prov>button.fchip{padding:8px 6px}
  .region-chip{padding:10px 10px;font-size:12px}
  .ico{width:14px;height:14px}
  .ico-sm{width:12px;height:12px}
  .st-none{min-width:14px;line-height:14px}
  /* Slash the 4-layer upward wash down to a single soft shadow on phones:
   * the desktop recipe re-rasterises four stacked blurs on every frame
   * while the slider's `width` + `transform` animate between chips, and
   * mobile GPUs choke on that workload — user-visible symptom is a
   * stuttery region-slider transition ("переключается рывками"). One
   * shadow layer keeps the "bloom of light from below" silhouette
   * without the per-frame compositing cost. Tfoot edge-* variants get
   * the same colour-tinted single-layer treatment. */
  .chip-track-bar>.chip-slider,
  .chip-track-prov>.chip-slider,
  .tbl-vpn tfoot .chip-track-prov>.chip-slider{box-shadow:0 -3px 14px rgba(197,15,53,.7)}
  .tbl-vpn tfoot .chip-track-prov.edge-ok>.chip-slider{box-shadow:0 -3px 14px rgba(34,197,94,.7)}
  .tbl-vpn tfoot .chip-track-prov.edge-half>.chip-slider{box-shadow:0 -3px 14px rgba(255,88,10,.7)}
  .tbl-vpn tfoot .chip-track-prov.edge-fail>.chip-slider{box-shadow:0 -3px 14px rgba(193,15,52,.7)}
  .chip-track-prov[data-brand="beeline"]>.chip-slider{box-shadow:0 -3px 14px rgba(252,181,51,.7)!important}
  .chip-track-prov[data-brand="mts"]>.chip-slider{box-shadow:0 -3px 14px rgba(255,0,0,.7)!important}
  .chip-track-prov[data-brand="megafon"]>.chip-slider{box-shadow:0 -3px 14px rgba(0,152,95,.7)!important}
  .chip-track-prov[data-brand="rostelecom"]>.chip-slider{box-shadow:0 -3px 14px rgba(236,93,47,.7)!important}
  /* VPN header on phones: force the dynamic legend (provider name /
   * connection type / last-check timestamp) to drop below the "Какой
   * VPN работает сегодня · N сервисов" caption instead of competing
   * with it for horizontal space. The desktop flex row still wraps
   * when items don't fit, but on mobile we lock the break so the
   * legend always gets its own line. `!important` is needed because
   * the wrapper carries inline `align-items:center` from its style
   * attribute — without it the column layout inherits center cross-
   * axis alignment and the caption ends up centred on mobile. */
  .vpn-hdr{flex-direction:column!important;align-items:flex-start!important;gap:4px}
  .vpn-hdr>.legend{width:100%}
}

/* Modal overlay: semi-opaque backdrop with blur. Clicking the backdrop
 * closes the modal (see `_modalBackdropClick`). The inner card keeps its
 * own background and lives in the markup so we only style the chrome
 * here. `.hidden` is the on/off switch. */
.modal-bg{background:rgba(4,3,5,.72);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}
.modal-bg.hidden{display:none!important}

/* Preloader */
#preloader{position:fixed;inset:0;z-index:99999;background:#0a0a0b;display:flex;align-items:center;justify-content:center;opacity:1;transition:opacity .6s cubic-bezier(.4,0,.2,1)}
#preloader::before{content:'';position:absolute;inset:0;background:var(--bg-url) center/cover no-repeat;opacity:1;pointer-events:none;animation:plBgDim 1.8s cubic-bezier(.4,0,.2,1) .2s forwards}
#preloader.done{opacity:0;pointer-events:none}
.pl-wrap{display:flex;align-items:stretch;gap:28px;position:relative;z-index:1}
.pl-icon{position:relative;width:160px;height:160px;flex-shrink:0;overflow:hidden}
.pl-rect{position:relative;width:160px;height:160px;clip-path:inset(100% 0 0 0);animation:plRevealRect .9s cubic-bezier(.22,1,.36,1) .15s forwards;border-radius:12px;overflow:hidden}
.pl-rect svg{width:100%;height:100%;display:block}
.pl-rect::after{content:'';position:absolute;inset:0;border-radius:12px;background:linear-gradient(135deg,rgba(255,255,255,.08) 0%,rgba(255,255,255,.03) 40%,transparent 55%);box-shadow:inset 0 0 0 1px rgba(255,255,255,.06),inset 0 1px 1px rgba(255,255,255,.07);pointer-events:none}
.pl-fist{position:absolute;bottom:0;left:50%;height:92%;aspect-ratio:166/231;transform:translate(-50%,40px);opacity:0;animation:plFistUp .85s cubic-bezier(.22,1,.36,1) .45s forwards}
.pl-fist svg,.pl-fist img{width:100%;height:100%;display:block;object-fit:contain;image-rendering:-webkit-optimize-contrast}
.pl-text{display:flex;flex-direction:column;justify-content:space-between;padding:6px 0}
.pl-title{height:115px;opacity:0;transform:translateX(60px);animation:plSlideText .85s cubic-bezier(.22,1,.36,1) .75s forwards}
.pl-title svg{height:100%;width:auto;display:block}
.pl-sub{height:16px;align-self:flex-end;opacity:0;transform:translateX(40px);animation:plSlideText .85s cubic-bezier(.22,1,.36,1) 1.05s forwards}
.pl-sub svg{height:100%;width:auto;display:block}
@keyframes plRevealRect{to{clip-path:inset(0 0 0 0)}}
@keyframes plFistUp{to{opacity:1;transform:translate(-50%,0%)}}
@keyframes plSlideText{to{opacity:1;transform:translateX(0)}}
@keyframes plBgDim{to{opacity:.4}}
@media(max-width:540px){
  .pl-wrap{gap:16px}
  .pl-icon{width:96px;height:96px}
  .pl-rect{width:96px;height:96px}
  .pl-fist{height:92%;transform:translate(-50%,24px)}
  .pl-text{padding:4px 0}
  .pl-title{height:68px}
  .pl-sub{height:10px}
}

/* Subscribe widget icon pulse */
@keyframes subPulse{0%,100%{filter:drop-shadow(0 0 4px rgba(197,15,53,.4))}50%{filter:drop-shadow(0 0 12px rgba(197,15,53,.8)) drop-shadow(0 0 24px rgba(197,15,53,.4))}}
