/* =========================================================================
   app.css — SolusTV CMS — dark theme matching the main project (index.html)
   Fonts: Inter (sans) + JetBrains Mono (mono). Accents: cyan + violet.
   ========================================================================= */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600;700&display=swap');

/* ==========================================================================================
   THEMES
   Every surface, border and text colour comes from here, so a theme is a palette swap and
   nothing else. Accent colours (primary/live/down/warn) stay constant across themes: they carry
   meaning, and a "danger" that changes hue between themes stops reading as danger.

   --raise-N are the translucent lifts used for hover states and subtle panels. They are white on
   dark themes and black on light, because "slightly lighter than the background" and "slightly
   darker than the background" are the same idea seen from opposite sides.
   ========================================================================================== */
:root{
  /* dark (default) — the original navy */
  --bg:#0b0f17; --panel:#0f1622; --surface:#141d2b; --inset:#0a0f18; --deep:#070b12;
  --line:#223047; --line-soft:#18212f; --line-strong:#3a4658;
  --text:#e7eef8; --muted:#8a9ab2; --faint:#5a6b84;
  --raise-1:rgba(255,255,255,.04); --raise-2:rgba(255,255,255,.07); --raise-3:rgba(255,255,255,.11);
  --shadow-soft:0 6px 18px rgba(0,0,0,.35); --shadow-hard:0 16px 40px rgba(0,0,0,.55);
  /* Base colour for hand-built multi-layer shadows: black on dark themes, a warm brown on
     cream so panels cast a shadow that belongs to the palette instead of a grey smudge. */
  --sh:0,0,0;
  --t-live:#34d399; --t-down:#f87171; --t-warn:#fbbf24; --t-info:#38bdf8; --t-violet:#c4b5fd;
  --src-hue:#22c55e; --src-hue-2:#4ade80; --src-text:#34d399;
  --accent-ink:var(--accent);
  --cat-text:#7cc4f5; --cat-bg:rgba(56,189,248,.13);
  /* The track listing reads like a console; each theme brings its own paper and ink. */
  --tk-bg:#0a1017; --tk-hdr:#7f8ea8; --tk-codec:#e7eef8; --tk-rest:#9fb0c8; --tk-num:#7dd3fc;
  /* Install-log ink. Was hard-coded mint (#b9e6c8), which vanished on the cream themes. */
  --log-text:#b9e6c8;
  --panel-2:#131c29;
  --primary:#38bdf8; --live:#34d399; --down:#fb7185; --warn:#fbbf24; --violet:#a78bfa;
  /* Form controls, floating menus and the sticky header. These existed only as hard-coded
     fallbacks inside var(--input) and friends -- which meant the fallback was ALWAYS
     what rendered, so every input and dropdown stayed navy on the cream and black themes.
     Declaring them here is what makes those two themes reach the controls at all. */
  --input:#0b1119; --input-line:var(--line);
  --accent:var(--primary); --accent-2:#0ea5e9;
  --nav-active:color-mix(in srgb,var(--accent) 16%,transparent); --nav-active-text:#bfe6fb;
  --menu-1:#182536; --menu-2:#111b28; --menu-line:#33465f;
  --topbar-bg:rgba(11,15,23,.82); --pick-invert:invert(1) opacity(.6);
  --btn-face:#0f141c;
  --ink:#04121c; --radius:13px;
  --mono:ui-monospace,"JetBrains Mono","SFMono-Regular",Menlo,Consolas,monospace;
  --sans:"Inter","Helvetica Neue",Helvetica,Arial,-apple-system,system-ui,"Segoe UI",Roboto,sans-serif;
  --sidebar-w:248px; --topbar-h:64px;
  color-scheme:dark;
}

/* black — for OLED panels and rooms with the lights off. Backgrounds collapse toward true black
   and the lines lift slightly to stay visible against it. */
:root[data-theme="black"]{
  --bg:#000000; --panel:#080808; --surface:#101010; --inset:#000000; --deep:#000000;
  --line:#242424; --line-soft:#161616; --line-strong:#3a3a3a;
  --text:#f2f2f2; --muted:#9a9a9a; --faint:#666666;
  --raise-1:rgba(255,255,255,.05); --raise-2:rgba(255,255,255,.09); --raise-3:rgba(255,255,255,.14);
  --shadow-soft:0 6px 18px rgba(0,0,0,.7); --shadow-hard:0 16px 40px rgba(0,0,0,.85);
  --sh:0,0,0;
  /* Lifted off pure black rather than matching it: a control that is the same colour as the
     page behind it has no edge, and on OLED that reads as a missing field. */
  --t-live:#34d399; --t-down:#f87171; --t-warn:#fbbf24; --t-info:#38bdf8; --t-violet:#c4b5fd;
  --src-hue:#22c55e; --src-hue-2:#4ade80; --src-text:#34d399;
  --accent-ink:var(--accent);
  /* Warm sand instead of the accent: on true black a saturated blue label vibrates and
     competes with the channel name it is only meant to annotate. */
  /* A deeper, quieter blue on its own tint -- readable on true black without the
     fluorescence of the raw accent. */
  --cat-text:#6fb3e8; --cat-bg:rgba(56,140,220,.16);
  --tk-bg:#070707; --tk-hdr:#7a7a7a; --tk-codec:#f2f2f2; --tk-rest:#a8a8a8; --tk-num:#d7b573;
  --panel-2:#111111;
  --input:#0d0d0d; --input-line:#2b2b2b;
  --accent:var(--primary); --accent-2:#0ea5e9;
  --nav-active:rgba(255,255,255,.10); --nav-active-text:#ffffff;
  --menu-1:#151515; --menu-2:#0b0b0b; --menu-line:#2e2e2e;
  --topbar-bg:rgba(0,0,0,.86); --pick-invert:invert(1) opacity(.6);
  --btn-face:#121212;
  --ink:#000000;
  color-scheme:dark;
}

/* light — warm cream rather than white. A pure-white panel next to bright video thumbnails is
   fatiguing over a long shift, and cream keeps contrast without the glare. Text is a deep slate,
   not black, for the same reason. */
:root[data-theme="light"]{
  --bg:#f5f1e8; --panel:#fdfbf6; --surface:#ffffff; --inset:#efeade; --deep:#e6e0d2;
  --line:#ddd5c4; --line-soft:#eae4d6; --line-strong:#c4b9a3;
  --text:#26303f; --muted:#6b7280; --faint:#9a9384;
  --raise-1:rgba(0,0,0,.03); --raise-2:rgba(0,0,0,.05); --raise-3:rgba(0,0,0,.08);
  --sh:90,75,45;
  --shadow-soft:0 6px 18px rgba(90,75,45,.10); --shadow-hard:0 16px 40px rgba(90,75,45,.18);
  /* Accents darkened just enough to stay legible on cream -- same hues, more contrast. */
  --primary:#b3541e; --live:#059669; --down:#e11d48; --warn:#b45309; --violet:#7c3aed;
  /* White fields on the cream page: the field is where you type, so it is the one thing that
     should be brighter than its surroundings. Menus sit a touch warmer so they read as
     floating above the page rather than as a hole cut in it. */
  /* On a bright page a pale mint or lemon is unreadable: same hue, taken down until it
     carries the contrast the text needs. The pill's tint is unchanged. */
  --t-live:#0a7a52; --t-down:#c02034; --t-warn:#8a5a05; --t-info:#1163b8; --t-violet:#5b39d6;
  /* The Source node is the origin of the tree, not a status -- on cream it wears the
     page's own maroon instead of a green that belonged to the dark palette. */
  --src-hue:#b3541e; --src-hue-2:#d0703a; --src-text:#8f3f13;
  --accent-ink:#7d3610;
  --cat-text:#1f5f96; --cat-bg:rgba(31,95,150,.11);
  /* Cream: a printed listing, not a terminal. Ink on paper, with the numbers picked out. */
  --tk-bg:#faf6ec; --tk-hdr:#8a7f6a; --tk-codec:#26303f; --tk-rest:#5f5849; --tk-num:#0f6b52;
  /* Same green as --tk-num: ink on paper, not a terminal's phosphor on cream. */
  --log-text:#0f6b52;
  --panel-2:#f6f2e8;
  --input:#ffffff; --input-line:#d6cdb9;
  --menu-1:#ffffff; --menu-2:#faf6ec; --menu-line:#d6cdb9;
  --topbar-bg:rgba(253,251,246,.86); --pick-invert:none;
  --btn-face:#ffffff;
  /* The accent for actions on cream. Sky blue on a warm page looked cold and washed out, so
     the primary BUTTON gets a burnt orange from the same family as the background. */
  --accent:#b3541e; --accent-2:#8f3f13;
  /* A warmer, DARKER wash than the page -- on cream a pale blue tint disappeared, and the
     selected page has to be the most obvious thing in the rail. */
  --nav-active:rgba(179,84,30,.16); --nav-active-text:#8f3f13;
  --ink:#ffffff;
  color-scheme:light;
}

/* light2 — a COOL white: paper-white surfaces on a blue-grey page, with a steel blue accent.
   The cream theme is warm and low-contrast by design; this one is the opposite choice, for
   bright rooms and screens where the beige read as dirty white. Same variable names, so every
   page follows without a single page-level rule. */
:root[data-theme="light2"]{
  --bg:#eef2f7; --panel:#ffffff; --surface:#ffffff; --inset:#f4f7fb; --deep:#e3eaf2;
  --line:#dae2ec; --line-soft:#eaeff6; --line-strong:#b6c4d6;
  /* Navy rather than black: pure black text on white vibrates, and the panel is read for
     hours at a time. */
  --text:#12253c; --muted:#5a6b81; --faint:#93a1b3;
  --raise-1:rgba(18,37,60,.03); --raise-2:rgba(18,37,60,.06); --raise-3:rgba(18,37,60,.09);
  --sh:30,58,95;
  --shadow-soft:0 6px 18px rgba(30,58,95,.10); --shadow-hard:0 16px 40px rgba(30,58,95,.18);
  /* Accents pulled down to stay legible on white -- the dark theme's sky blue is invisible
     against it. */
  --primary:#1d6fd0; --live:#0e9f6e; --down:#dc2f4b; --warn:#b45309; --violet:#6d4aff;
  --t-live:#067a52; --t-down:#b3253c; --t-warn:#8a5a05; --t-info:#1558a8; --t-violet:#5b39d6;
  --src-hue:#8a3b16; --src-hue-2:#b3541e; --src-text:#7a3312;
  --accent-ink:#0f4c92;
  --cat-text:#0f4c92; --cat-bg:rgba(15,76,146,.10);
  --tk-bg:#f5f8fc; --tk-hdr:#7488a0; --tk-codec:#12253c; --tk-rest:#4a5a6e; --tk-num:#0f4c92;
  /* Deep green with the contrast white needs -- the mint of the dark themes disappears here. */
  --log-text:#067a52;
  --panel-2:#f3f7fb;
  --input:#ffffff; --input-line:#cdd9e7;
  --accent:#1d6fd0; --accent-2:#1558a8;
  --nav-active:rgba(29,111,208,.13); --nav-active-text:#1558a8;
  --menu-1:#ffffff; --menu-2:#f6f9fd; --menu-line:#d5e0ec;
  --topbar-bg:rgba(255,255,255,.88); --pick-invert:none;
  --btn-face:#ffffff;
  --ink:#ffffff;
  color-scheme:light;
}
*{box-sizing:border-box}
html,body{height:100%}
/* clip, not hidden. `overflow-x:hidden` makes overflow-y compute to auto, which turns <html>
   into a scroll container -- and then position:sticky has nothing to stick to, so the sidebar
   scrolled away with the page. `clip` cuts the same horizontal overflow without creating a
   scroll container, so sticky keeps working. */
html{overflow-x:clip;max-width:100%}
/* If a browser is too old for `clip` it simply ignores the line above and keeps overflow
   visible -- which is the right trade: a rare sideways scrollbar beats a sidebar that scrolls
   away on every page. The widths below stop the overflow at its source anyway. */
.app,.main,.content{max-width:100%;min-width:0}
body{
  margin:0;font-family:var(--sans);color:var(--text);font-size:14px;
  /* Same reason as <html> above: hidden would make this a scroll container and break the
     sticky sidebar. clip trims the same horizontal overflow without that side effect. */
  max-width:100%;overflow-x:clip;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
  font-feature-settings:"cv02","cv03","cv04","cv11";letter-spacing:-0.011em;
  background:
    radial-gradient(1200px 600px at 78% -8%, color-mix(in srgb,var(--accent) 7%,transparent), transparent 60%),
    radial-gradient(900px 500px at 0% 0%, rgba(167,139,250,.06), transparent 55%),
    var(--bg);
}
a{color:inherit;text-decoration:none}
h1,h2,h3{margin:0}
.mono{font-family:var(--mono)}
.muted{color:var(--muted)}
.faint{color:var(--faint)}
.eyebrow{font-size:10.5px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:var(--faint)}

/* ===== App shell ===== */
.app{display:grid;grid-template-columns:var(--sidebar-w) 1fr;min-height:100vh}
.sidebar{
  /* fixed, not sticky. Sticky depends on every ancestor keeping overflow visible and on the
     grid area being taller than the item -- three separate things that can each quietly break
     it, and one of them did. Fixed answers to nothing but the viewport. The grid column below
     still reserves the width, so the content is not covered. */
  position:fixed;top:0;left:0;width:var(--sidebar-w);height:100vh;height:100dvh;
  background:linear-gradient(180deg,var(--inset),var(--inset));border-right:1px solid var(--line);
  display:flex;flex-direction:column;z-index:120;
  /* The sidebar itself never scrolls -- only the middle does. */
  overflow:hidden}
/* min-height:0 is what actually lets a flex item shrink below its content and scroll;
   without it the nav simply grows and pushes the footer out of the box. */
.sb-scroll{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;
  /* Hidden, not thin. A global rule further down sets scrollbar-width thin on every element,
     and the moment Chrome sees that property it ignores all ::-webkit-scrollbar styling for
     the element -- so asking for three pixels quietly produced Chrome's own thin, about
     eleven. The standard property accepts only auto, thin or none, so none is the only step
     below thin. The nav still scrolls exactly as before; only the bar itself is gone. */
  scrollbar-width:none;-ms-overflow-style:none}
.sb-scroll::-webkit-scrollbar{width:0;height:0;display:none}
.sb-brand{display:flex;align-items:center;gap:12px;padding:20px 18px 18px;
  border-bottom:1px solid var(--line-soft);flex:0 0 auto}
.sb-logo{height:34px;width:auto;max-width:100%}
.dot-logo{width:38px;height:38px;border-radius:11px;background:linear-gradient(140deg,var(--primary),var(--violet));
  display:grid;place-items:center;color:var(--ink);box-shadow:0 6px 18px color-mix(in srgb,var(--accent) 30%,transparent);flex:none}
.dot-logo svg{width:21px;height:21px}
.sb-brand h1{margin:0;font-size:16px;font-weight:800;letter-spacing:.02em;line-height:1}
.sb-brand .sub{margin-top:4px;font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:var(--faint)}
.sb-section{padding:18px 12px 6px}
.sb-section .lbl{font-size:9.5px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:var(--faint);padding:0 10px 8px}
.nav-item{display:flex;align-items:center;gap:12px;padding:10px 12px;border-radius:10px;
  color:var(--muted);font-size:13.5px;font-weight:600;cursor:pointer;
  transition:background .14s,color .14s;position:relative;margin-bottom:2px}
.nav-item svg{width:18px;height:18px;flex:none}
.nav-item:hover{background:color-mix(in srgb,var(--accent) 7%,transparent);color:var(--text)}
.nav-item.active{background:linear-gradient(90deg,var(--nav-active),transparent);color:var(--nav-active-text)}

/* Logo per theme. Both files ship in the page and CSS picks one: src cannot be swapped from a
   stylesheet, and swapping it in JS would paint the wrong logo for a frame on every load --
   exactly the flash the pre-paint theme script exists to prevent. The white wordmark
   disappears on cream, the dark one disappears on black, so this is not decoration. */
/* A combobox has to LOOK like one. These widgets are styled as inputs and open a list on
   click, but without the caret a <select> has they read as plain text fields -- so nobody
   clicks them. Drawn in CSS (a mask, so it follows the text colour and every theme) rather
   than as markup in each page, which would mean editing every widget that ever gets added. */
.ffp-box, .fw-cc-box{ padding-right:30px !important; position:relative; }
.ffp, .fw-cc{ position:relative; }
.ffp::after, .fw-cc::after{
  content:""; position:absolute; right:11px; top:19px; width:11px; height:11px; margin-top:-5px;
  pointer-events:none; background-color:var(--muted); transition:transform .16s, background-color .14s;
  -webkit-mask:no-repeat center/11px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  mask:no-repeat center/11px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); }
/* Open = pointing the other way, the same feedback a native select gives. */
.ffp.open::after, .fw-cc.open::after{ transform:rotate(180deg); background-color:var(--accent); }
.ffp.is-off::after{ opacity:.4; }

.logo-light{display:none}
:root[data-theme="light"] .logo-dark,
:root[data-theme="light2"] .logo-dark{display:none}
:root[data-theme="light"] .logo-light,
:root[data-theme="light2"] .logo-light{display:inline-block}
.nav-item.active::before{content:"";position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:0 3px 3px 0;background:var(--primary)}
/* "Add" action row above a nav item — styled like a normal nav item, + icon only */
.sb-add{display:flex;align-items:center;gap:12px;width:100%;padding:10px 12px;margin-bottom:2px;
  border:0;background:transparent;color:var(--muted);border-radius:10px;font-family:inherit;
  font-size:13.5px;font-weight:600;cursor:pointer;transition:background .14s,color .14s}
.sb-add:hover{background:color-mix(in srgb,var(--accent) 7%,transparent);color:var(--text)}
.sb-add svg{width:18px;height:18px;flex:none}
.sb-foot{border-top:1px solid var(--line-soft);padding:9px 14px;flex:0 0 auto;
  background:var(--inset)}
.me{display:flex;align-items:center;gap:11px;margin-bottom:11px}
.me-av{width:38px;height:38px;border-radius:10px;background:linear-gradient(140deg,var(--primary),var(--violet));
  color:var(--ink);display:grid;place-items:center;font-weight:800;flex:none}
.me-name{font-weight:700;font-size:13.5px}
.me-role{font-size:11px;color:var(--faint);text-transform:capitalize}
.btn-logout{display:block;text-align:center;padding:9px;border:1px solid var(--line);border-radius:9px;
  color:var(--muted);font-weight:600;font-size:13px;transition:.14s}
.btn-logout:hover{border-color:var(--down);color:var(--down)}

/* ===== Main ===== */
/* Explicit placement, because the sidebar is position:fixed and therefore not a grid item at
   all. Left to auto-placement, .main became the first in-flow child and landed in column one
   -- the 248px sidebar column -- so the content was squeezed underneath the sidebar. */
.main{grid-column:2;min-width:0;display:flex;flex-direction:column}
.topbar{height:var(--topbar-h);display:flex;align-items:center;justify-content:space-between;
  padding:0 26px;border-bottom:1px solid var(--line);position:sticky;top:0;z-index:50;
  background:var(--topbar-bg);backdrop-filter:blur(12px)}
.topbar h2{font-size:16px;font-weight:800;letter-spacing:.01em}
.clock{font-family:var(--mono);font-size:12.5px;color:var(--muted)}
.content{padding:24px 28px;width:100%}

/* ===== Cards ===== */
.card{background:linear-gradient(180deg,var(--panel),var(--panel));border:1px solid var(--line);
  border-radius:var(--radius);box-shadow:0 1px 0 rgba(255,255,255,.02) inset, var(--shadow-soft);margin-bottom:18px}
.card-head{padding:15px 18px;border-bottom:1px solid var(--line-soft);display:flex;align-items:center;justify-content:space-between;gap:12px}
.card-head h2{font-size:14px;font-weight:700}
.card-body{padding:18px}

/* stat cards */
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:18px}
.stat{background:linear-gradient(180deg,var(--panel),var(--panel));border:1px solid var(--line);border-radius:var(--radius);
  padding:16px 18px;display:flex;align-items:center;gap:14px;position:relative;overflow:hidden}
.stat::after{content:"";position:absolute;inset:0 0 auto 0;height:2px;opacity:.85}
.stat.s-blue::after{background:linear-gradient(90deg,var(--primary),transparent)}
.stat.s-green::after{background:linear-gradient(90deg,var(--live),transparent)}
.stat.s-amber::after{background:linear-gradient(90deg,var(--warn),transparent)}
.stat.s-red::after{background:linear-gradient(90deg,var(--down),transparent)}
.stat .ico{width:44px;height:44px;border-radius:12px;display:grid;place-items:center;flex:none}
.stat.s-blue .ico{background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--primary)}
.stat.s-green .ico{background:rgba(52,211,153,.14);color:var(--live)}
.stat.s-amber .ico{background:rgba(251,191,36,.14);color:var(--warn)}
.stat.s-red .ico{background:rgba(251,113,133,.14);color:var(--down)}
.stat .ico svg{width:22px;height:22px}
.stat .v{font-size:26px;font-weight:800;line-height:1;font-family:var(--mono)}
.stat .l{color:var(--muted);font-size:12.5px;margin-top:5px}

/* ===== Buttons ===== */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:9px 15px;border-radius:9px;
  border:1px solid var(--line);background:var(--surface);color:var(--text);font-weight:600;font-size:13px;
  cursor:pointer;font-family:var(--sans);transition:.14s;white-space:nowrap}
.btn:hover{background:var(--surface);border-color:var(--line)}
.btn svg{width:16px;height:16px}
.btn-primary{background:linear-gradient(140deg,var(--accent),var(--accent-2));border-color:transparent;color:#fff;font-weight:700}
.btn-primary:hover{filter:brightness(1.07);background:linear-gradient(140deg,var(--accent),var(--accent-2))}

/* On the black theme the filled sky-blue button was the one loud thing on an otherwise
   monochrome page, so here it wears a plain face instead of a slab of colour.
   
   It used to keep a thin ACCENT hairline for emphasis, and that hairline was still blue --
   which shows up hardest where a button sits against a field, as Pick from does on the movie
   form: one blue edge and one grey one, on a theme that has no other blue in it. It now takes
   the FIELD's border, so a button and the input beside it are the same object drawn twice.
   Emphasis comes from weight and the lift on hover, which this theme has plenty of.
   
   Hover frosts it: a touch of light through the surface rather than a colour change. */
:root[data-theme="black"] .btn-primary{
  background:var(--btn-face); color:var(--text);
  border:1px solid var(--input-line);
  box-shadow:0 1px 0 rgba(255,255,255,.04) inset; }
:root[data-theme="black"] .btn-primary:hover{
  filter:none;
  background:linear-gradient(180deg,rgba(255,255,255,.10),rgba(255,255,255,.03));
  border-color:var(--line-strong);
  box-shadow:0 1px 0 rgba(255,255,255,.10) inset;
  backdrop-filter:blur(2px); }
:root[data-theme="black"] .btn-primary:active{ transform:translateY(1px); }
.btn-danger{color:var(--down);border-color:rgba(251,113,133,.4)}
.btn-danger:hover{background:rgba(251,113,133,.1);border-color:var(--down)}
.btn-sm{padding:6px 11px;font-size:12px;border-radius:8px}
.btn-block{width:100%}

/* ===== Alerts ===== */
.alert{padding:12px 14px;border-radius:10px;margin-bottom:16px;font-weight:500;font-size:13.5px;border:1px solid transparent}
.alert-success{background:rgba(52,211,153,.1);color:var(--t-live);border-color:rgba(52,211,153,.3)}
.alert-error{background:rgba(251,113,133,.1);color:var(--t-down);border-color:rgba(251,113,133,.3)}

/* ===== Forms ===== */
.field{display:block;margin-bottom:15px}
.field-label{display:block;font-weight:600;font-size:12.5px;margin-bottom:7px;color:var(--text)}
input[type=text],input[type=password],input[type=email],input[type=number],input[type=date],
input[type=search],input[type=url],select,textarea{
  width:100%;padding:10px 13px;border:1px solid var(--input-line);border-radius:9px;font-size:13.5px;
  font-family:var(--sans);background:var(--input);color:var(--text);outline:none;transition:.14s}
/* The native date/search widgets paint their own glyphs from color-scheme; without this they
   stay white-on-white on cream. */
input[type=date]::-webkit-calendar-picker-indicator{filter:var(--pick-invert)}
input::placeholder,textarea::placeholder{color:var(--faint)}
input:focus,select:focus,textarea:focus{border-color:var(--accent);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 18%,transparent)}
select{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23888888' stroke-width='2'%3E%3Cpath d='M2 4l4 4 4-4'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 13px center}
textarea{resize:vertical}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}

/* ===== Port picker (tag input) =====
   Same language as the firewall's country picker: a box that behaves like an input, holding
   chips. Ports are ORDERED here -- the first is the primary -- so the first chip wears a label
   and the others invite a click to take its place. */
.pt-pick{position:relative}
.pt-box{display:flex;flex-wrap:wrap;align-items:center;gap:6px;min-height:42px;
  padding:7px 10px;border-radius:9px;border:1px solid var(--input-line);
  background:var(--input);cursor:text;transition:border-color .14s,box-shadow .14s}
.pt-box:focus-within{border-color:var(--accent);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 18%,transparent)}
.pt-box input{flex:1;min-width:110px;border:0 !important;background:none !important;
  padding:2px 0 !important;font:inherit;font-size:13px;color:var(--text);
  outline:none;box-shadow:none !important}
.pt-chip{display:inline-flex;align-items:center;gap:6px;padding:3px 7px 3px 9px;
  border-radius:7px;border:1px solid var(--line-strong);background:var(--panel);
  font-family:var(--mono);font-size:12.5px;color:var(--text);cursor:pointer;
  transition:border-color .13s,background .13s}
.pt-chip:hover{border-color:color-mix(in srgb,var(--accent) 55%,transparent)}
.pt-chip.is-primary{border-color:color-mix(in srgb,var(--accent) 45%,transparent);
  background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent-ink);
  cursor:default}
.pt-chip.is-primary:hover{border-color:color-mix(in srgb,var(--accent) 45%,transparent)}
.pt-chip b{font-weight:600}
.pt-tag{font-family:var(--sans);font-style:normal;font-size:9.5px;font-weight:700;
  letter-spacing:.05em;text-transform:uppercase;color:var(--accent-ink);opacity:.75}
.pt-chip .x{color:var(--muted);font-size:14px;line-height:1;padding:0 2px;border-radius:4px;
  font-family:var(--sans)}
.pt-chip .x:hover{color:var(--t-down);background:color-mix(in srgb,#ef4444 18%,transparent)}
.pt-hint{display:block;margin-top:7px;color:var(--muted);font-size:11.5px;line-height:1.5}
.pt-hint b{color:var(--text);font-weight:600}

/* ===== Login ===== */
.login-wrap{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px}
.login-card{background:linear-gradient(180deg,var(--panel),var(--panel));border:1px solid var(--line);border-radius:16px;
  width:392px;max-width:94vw;padding:34px;box-shadow:var(--shadow-soft)}
.login-brand{display:flex;align-items:center;gap:12px;margin-bottom:24px}
.login-card h1{font-size:21px;font-weight:800}
.login-sub{color:var(--muted);margin:3px 0 24px;font-size:13.5px}
.login-foot{margin-top:22px;color:var(--faint);font-size:12px}

/* ===== Tables ===== */
.tbl{width:100%;border-collapse:collapse}
.tbl th{text-align:left;font-size:10.5px;letter-spacing:.1em;color:var(--faint);text-transform:uppercase;
  padding:8px 16px;border-bottom:1px solid var(--line)}
.tbl td{padding:9px 16px;border-bottom:1px solid var(--line-soft);font-size:13.5px}
.tbl tr:last-child td{border-bottom:none}
.tbl tbody tr{transition:background .12s}
.tbl tbody tr:hover{background:color-mix(in srgb,var(--accent) 4%,transparent)}
.tbl a.slug{font-family:var(--mono);font-weight:700;color:var(--t-info)}

/* badges */
.badge{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:999px;font-size:11.5px;font-weight:700}
.badge::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
.badge-green{background:rgba(52,211,153,.12);color:var(--t-live)}
.badge-amber{background:rgba(251,191,36,.12);color:var(--t-warn)}
.badge-red{background:rgba(251,113,133,.12);color:var(--t-down)}
.badge-gray{background:rgba(138,154,178,.12);color:var(--muted)}
.badge-orange{background:rgba(251,146,60,.14);color:#fdba74}
.badge-black{background:rgba(15,20,30,.85);color:var(--text);border:1px solid var(--line)}
.badge-black::before{background:var(--faint)}

/* key-value */
.kv{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--line-soft);font-size:13.5px;align-items:center}
.kv:last-child{border-bottom:none}
.kv>span:first-child{color:var(--muted);font-weight:500;flex:0 0 120px}
.kv code,code.copyable{background:var(--inset);border:1px solid var(--line-soft);padding:4px 9px;border-radius:7px;
  font-family:var(--mono);font-size:12.5px;color:var(--text);word-break:break-all;cursor:pointer}
.kv code:hover,code.copyable:hover{border-color:var(--primary)}

/* page header row */
.page-head{display:flex;align-items:center;justify-content:space-between;gap:14px;margin-bottom:18px}
.page-head .left{display:flex;align-items:center;gap:12px}
.page-head h2{font-size:18px;font-weight:800}
.empty{padding:54px 20px;text-align:center;color:var(--muted)}
.empty svg{width:42px;height:42px;color:var(--faint);margin-bottom:12px}

/* ===== Mobile menu button (hidden on desktop) ===== */
.menu-btn{display:none;width:40px;height:40px;border-radius:9px;border:1px solid var(--line);
  background:var(--surface);color:var(--text);cursor:pointer;align-items:center;justify-content:center;flex:none}
.menu-btn svg{width:20px;height:20px}
.sidebar-backdrop{display:none;position:fixed;inset:0;background:rgba(4,8,15,.6);z-index:110}
.sidebar-backdrop.open{display:block}

/* Tables scroll horizontally on small screens instead of overflowing */
.table-wrap{width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}

@media(max-width:900px){
  .app{grid-template-columns:1fr}
  /* One column here, so the explicit placement above has to be undone. */
  .main{grid-column:1}
  .sidebar{position:fixed;left:-280px;width:264px;transition:left .22s ease;box-shadow:var(--shadow-hard)}
  .sidebar.open{left:0}
  .menu-btn{display:inline-flex}
  .stats{grid-template-columns:repeat(2,1fr)}
  .form-grid{grid-template-columns:1fr}
  .content{padding:18px 16px}
  .topbar{padding:0 16px}
  .topbar h2{font-size:15px}
  .page-head{flex-wrap:wrap}
  /* stacked detail grids on mobile */
  .kv>span:first-child{flex:0 0 110px}
}

@media(max-width:560px){
  .stats{grid-template-columns:1fr}
  .gen-grid{grid-template-columns:1fr}
  .clock{display:none}
  .page-head .left{flex-wrap:wrap}
  .modal{width:100%}
  .modal-overlay{padding:16px 8px}
  /* make action button rows wrap nicely */
  .page-head > div{flex-wrap:wrap}
  /* two-column detail grids collapse */
  div[style*="grid-template-columns:1fr 1fr"]{grid-template-columns:1fr !important}
}

/* ===== Modal ===== */
.modal-overlay{position:fixed;inset:0;background:rgba(4,8,15,.72);backdrop-filter:blur(3px);
  display:none;align-items:flex-start;justify-content:center;z-index:300;padding:48px 16px;overflow:auto}
.modal-overlay.open{display:flex}
.modal{background:linear-gradient(180deg,var(--panel),var(--panel));border:1px solid var(--line);
  border-radius:15px;width:640px;max-width:96vw;box-shadow:var(--shadow-hard);
  animation:modalIn .16s ease}
@keyframes modalIn{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:none}}
.modal-head{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--line-soft)}
.modal-head h2{font-size:15px;font-weight:800}
.modal-x{width:30px;height:30px;border-radius:8px;border:1px solid var(--line);background:var(--surface);
  color:var(--muted);cursor:pointer;display:grid;place-items:center;font-size:16px;line-height:1}
.modal-x:hover{color:var(--text);border-color:var(--down)}
.modal-body{padding:20px}
.modal-foot{display:flex;gap:10px;justify-content:flex-end;padding:16px 20px;border-top:1px solid var(--line-soft)}

/* result card after creation */
.created-box{background:var(--inset);border:1px solid rgba(52,211,153,.3);border-radius:11px;padding:16px;margin-top:4px}
.created-box .kv{border-color:var(--line)}
.spinner{width:15px;height:15px;border:2px solid rgba(4,18,28,.3);border-top-color:var(--ink);border-radius:50%;
  display:inline-block;animation:spin .6s linear infinite;vertical-align:-2px}
@keyframes spin{to{transform:rotate(360deg)}}

/* ===== Generated credential fields (create modal) ===== */
.gen-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px}
.input-gen{display:flex;gap:6px;align-items:stretch}
.input-gen input{flex:1}
.gen-btn{flex:none;padding:0 11px;font-size:15px;line-height:1}
@media(max-width:760px){.gen-grid{grid-template-columns:1fr}}

/* ===== Install log (live terminal) ===== */
.install-log{background:var(--deep);border:1px solid var(--line);border-radius:11px;padding:14px 16px;
  font-family:var(--mono);font-size:12.5px;line-height:1.7;color:var(--log-text);max-height:420px;overflow:auto;
  white-space:pre;margin:0;
  scrollbar-width:thin;scrollbar-color:rgba(148,163,184,.35) transparent}
.install-log::-webkit-scrollbar{width:8px;height:8px}
.install-log::-webkit-scrollbar-track{background:transparent}
.install-log::-webkit-scrollbar-thumb{background:rgba(148,163,184,.28);border-radius:8px}
.install-log::-webkit-scrollbar-thumb:hover{background:rgba(148,163,184,.5)}
.install-log::-webkit-scrollbar-corner{background:transparent}

/* ===== Progress bar (install) ===== */
.progress-wrap{width:100%;height:10px;background:var(--inset);border:1px solid var(--line);border-radius:999px;overflow:hidden;margin:6px 0 4px}
.progress-bar{height:100%;width:0;border-radius:999px;background:linear-gradient(90deg,var(--primary),var(--violet));
  transition:width .5s ease;box-shadow:0 0 12px color-mix(in srgb,var(--accent) 50%,transparent)}
.progress-pct{text-align:right;font-size:12px;color:var(--muted);margin-bottom:12px}

/* server cards section replaced below */
/* ===== Server table (xtream-style) ===== */
.srv-table th{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);white-space:nowrap}
.srv-table td{vertical-align:middle;font-size:12.5px;white-space:nowrap}
.dot{display:inline-block;width:10px;height:10px;border-radius:50%}
.dot-up{background:var(--live);box-shadow:0 0 8px rgba(34,197,94,.7)}
.dot-down{background:var(--down);box-shadow:0 0 8px rgba(239,68,68,.6)}
.tag-main{font-size:9px;font-weight:800;background:var(--primary);color:var(--ink);padding:1px 5px;border-radius:5px;vertical-align:middle;letter-spacing:.03em}
.cell-meter{display:flex;align-items:center;gap:7px;min-width:92px}
.cell-track{flex:1;height:7px;background:var(--inset);border:1px solid var(--line-soft);border-radius:999px;overflow:hidden}
.cell-fill{height:100%;width:0;border-radius:999px;background:linear-gradient(90deg,var(--live),#22c55e)}
.cell-fill.warn{background:linear-gradient(90deg,var(--warn),#f59e0b)}
.cell-fill.hot{background:linear-gradient(90deg,var(--down),#ef4444)}
.cell-meter .mono{font-size:11px;color:var(--muted);min-width:30px;text-align:right}

/* ===== Header icons ===== */
.th-ico{display:inline-flex;align-items:center;gap:5px}
.th-ico svg{width:13px;height:13px;opacity:.75;flex:none}
/* inline label icons (cards, rows) */
.lbl-ico{display:inline-flex;align-items:center;gap:6px}
.lbl-ico svg{width:14px;height:14px;opacity:.8;flex:none}
.meter-l svg,.srv-k svg{width:13px;height:13px;opacity:.8;vertical-align:-2px;margin-right:5px}

/* ===== Server table card (better contrast vs page bg) ===== */
.srv-table-card{background:linear-gradient(180deg,var(--panel),var(--panel));border:1px solid var(--line);
  /* clip, not hidden: `overflow:hidden` makes this a scroll container, and a sticky toolbar
     inside one sticks to the CARD rather than the viewport -- the same trap that broke the
     sidebar earlier. `clip` still rounds the corners without creating that container. */
  border-radius:var(--radius);overflow:clip;box-shadow:var(--shadow-soft)}
.srv-table-card .table-wrap{border-radius:var(--radius)}
.srv-table thead tr{background:color-mix(in srgb,var(--accent) 5%,transparent)}
.srv-table th{padding:11px 16px;border-bottom:1px solid var(--line)}
.srv-table tbody tr{transition:background .12s}
.srv-table tbody tr:nth-child(even){background:rgba(255,255,255,.012)}
.srv-table tbody tr:hover{background:color-mix(in srgb,var(--accent) 6%,transparent)}
.srv-table td{padding:12px 16px;border-bottom:1px solid var(--line-soft)}
.srv-table tbody tr:last-child td{border-bottom:none}

/* ===== Spinner (button loading) ===== */
.spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.35);
  border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;vertical-align:-2px;margin-left:7px}
@keyframes spin{to{transform:rotate(360deg)}}
.btn:disabled{opacity:.75;cursor:wait}

/* install/progress sections inside the new padded-0 card */
#srvInstall{padding:0 18px 14px}
#srvFail{padding:0 18px 14px}
.srv-card .progress-wrap{margin:0 0 4px}

/* ===== Country flag in tables ===== */
.fi{border-radius:2px;box-shadow:0 0 0 1px var(--raise-2);vertical-align:-1px;width:20px;line-height:15px}
.flag-code{font-family:var(--mono);font-size:11px;color:var(--muted);margin-left:2px}

/* ===== Unified status badges (server table) ===== */
.st-badge{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;font-weight:700;padding:3px 9px;border-radius:999px;white-space:nowrap}
.st-ok{background:rgba(34,197,94,.12);color:var(--t-live);border:1px solid rgba(34,197,94,.3)}
.st-installing{background:rgba(245,158,11,.12);color:var(--t-warn);border:1px solid rgba(245,158,11,.3)}
.st-fail{background:rgba(239,68,68,.12);color:var(--t-down);border:1px solid rgba(239,68,68,.3)}
.st-off{background:rgba(148,163,184,.1);color:var(--muted);border:1px solid var(--line)}
.spin-mini{display:inline-block;width:10px;height:10px;border:2px solid rgba(251,191,36,.35);
  border-top-color:#fbbf24;border-radius:50%;animation:spin .6s linear infinite}


/* =====================================================================
   SERVER CARDS — fresh design
   Layout: left status rail · header row (name + pill) · stat tiles · meters
   Calm, modern, no heavy glow. Status communicated by a left rail + pill.
   ===================================================================== */
.srv-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(380px,1fr));gap:18px}
/* the LB-cards wrapper must not occupy its own grid cell, otherwise an empty
   cell appears between the main card and the "add" card */
#lbCards{display:contents}
@media(max-width:560px){.srv-grid{grid-template-columns:1fr}}

.srv-card{position:relative;height:340px;display:flex;flex-direction:column;
  background:linear-gradient(180deg,var(--surface),var(--panel));
  border:1px solid var(--line);border-radius:16px;overflow:hidden;padding:0;
  transition:border-color .2s ease, transform .2s ease, box-shadow .2s ease;
  box-shadow:
    0 1px 0 var(--raise-1) inset,
    0 0 0 1px rgba(var(--sh),.4),
    0 18px 40px -8px rgba(var(--sh),.55),
    0 6px 14px rgba(var(--sh),.4)}
.srv-card:hover{transform:translateY(-3px);border-color:#3a4f6e;
  box-shadow:
    0 1px 0 var(--raise-2) inset,
    0 0 0 1px rgba(var(--sh),.4),
    0 28px 60px -10px rgba(var(--sh),.65),
    0 10px 22px rgba(var(--sh),.5)}
/* left status rail (thicker) */
.srv-card::before{content:"";position:absolute;left:0;top:0;bottom:0;width:5px;background:var(--line-strong);z-index:1}
.srv-card.is-online::before{background:linear-gradient(180deg,#34d399,#10b981)}
.srv-card.is-installing::before{background:linear-gradient(180deg,#fbbf24,#f59e0b)}
.srv-card.is-failed::before{background:linear-gradient(180deg,#f87171,#ef4444)}
.srv-card.is-offline::before{background:linear-gradient(180deg,#f87171,#ef4444)}
/* remove any leftover sweep */
.srv-card::after{display:none}

/* header: name+ssh on the left, status pill on the right */
.srv-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex:0 0 auto;
  text-align:left;padding:11px 18px 11px 20px;border-bottom:1px solid var(--line);
  background:linear-gradient(180deg,rgba(255,255,255,.025),transparent)}
.srv-head-left{display:flex;flex-direction:column;gap:4px;min-width:0}
.srv-name{font-size:15px;font-weight:700;letter-spacing:-.01em;color:var(--text);
  display:flex;align-items:center;gap:6px;min-width:0;flex-wrap:nowrap}
/* The name is the only part of this line allowed to give way. A long one used to wrap onto a
   second line, which grew the header and pushed every number under it out of place -- so it
   truncates instead, and the full name is on its own tooltip. The dot and the NVIDIA badge
   never shrink: a half-drawn status lamp or a clipped brand mark reads as a broken card. */
.srv-name-txt{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.srv-name-dot{flex:0 0 auto}
/* The badge rides on the second line now, beside the IP: on the first it was taking 80px from a
   300px card and leaving the name six characters. Never shrinks, never wraps -- a clipped brand
   mark reads as a rendering fault. */
.srv-sub{min-width:0;flex-wrap:wrap;row-gap:5px}
.srv-sub .srv-nv-badge{flex:0 0 auto;margin-left:1px}
/* The IP is never truncated. It is thirteen characters the operator copies into PuTTY, and half
   an address is worse than no address -- if the line runs out of room the badge wraps under it
   instead. The card has a fixed height, so a taller header costs body space, not alignment. */
.srv-sub .srv-ip{flex:0 0 auto;white-space:nowrap}
.srv-name::before{display:none}
.srv-ssh{font-size:11px;color:var(--faint);font-weight:400;letter-spacing:0;margin:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.srv-sub{display:flex;align-items:center;gap:7px;margin-top:2px}
.srv-sub .fi{width:18px;height:13px;border-radius:2px;flex:0 0 auto;box-shadow:var(--shadow-soft)}
.srv-ip{font-size:12px;color:var(--muted);font-family:var(--mono);letter-spacing:.01em;
  background:var(--raise-1);padding:1px 7px;border-radius:5px;border:1px solid var(--line-soft,rgba(148,163,184,.12))}
.srv-name .fi{margin-left:2px;vertical-align:-1px}

/* status pill (right side of header) */
.srv-status-pill{display:inline-flex;align-items:center;gap:7px;flex:0 0 auto;
  font-size:11px;font-weight:600;letter-spacing:.01em;padding:5px 11px 5px 9px;border-radius:8px;
  background:rgba(148,163,184,.1);color:var(--muted);border:1px solid var(--line);text-transform:none;margin:0}
.srv-status-pill::before{content:"";width:7px;height:7px;border-radius:50%;background:currentColor;flex:0 0 auto}
.srv-card.is-online .srv-status-pill{background:color-mix(in srgb,var(--t-live) 12%,transparent);color:var(--t-live);border-color:color-mix(in srgb,var(--t-live) 28%,transparent)}
.srv-card.is-installing .srv-status-pill{background:rgba(245,158,11,.12);color:var(--t-warn);border-color:rgba(245,158,11,.28)}
.srv-card.is-failed .srv-status-pill{background:rgba(239,68,68,.12);color:var(--t-down);border-color:rgba(239,68,68,.28)}
.srv-card.is-offline .srv-status-pill{background:rgba(239,68,68,.12);color:var(--t-down);border-color:rgba(239,68,68,.28)}
/* gentle pulse only on the online dot */
.srv-card.is-online .srv-status-pill::before{animation:dotPulse 2s ease-in-out infinite}
.srv-card.is-installing .srv-status-pill::before{animation:dotPulse 1s ease-in-out infinite}
@keyframes dotPulse{0%,100%{opacity:1;box-shadow:0 0 0 0 currentColor}50%{opacity:.5;box-shadow:0 0 6px 1px currentColor}}

/* top stat row -> 3 tiles (Users / Connections / Uptime) */
.srv-rows{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;margin:0;padding:0;
  background:var(--line-soft);border-bottom:1px solid var(--line-soft)}
.srv-row{display:flex;flex-direction:column;align-items:flex-start;gap:2px;
  padding:8px 16px 7px;background:var(--panel);border:none}
.srv-row:last-child{border:none}
.srv-k{order:2;color:var(--faint);font-size:9.5px;font-weight:500;letter-spacing:.04em;
  text-transform:uppercase;display:inline-flex;align-items:center;gap:4px}
.srv-k svg{width:11px;height:11px}
.srv-k svg{width:12px;height:12px;opacity:.6}
.srv-v{order:1;font-size:19px;font-weight:700;letter-spacing:-.02em;color:var(--text);
  font-variant-numeric:tabular-nums;line-height:1.1;white-space:nowrap;overflow:hidden;text-overflow:clip}
.srv-row{min-width:0}

/* meters block */
/* The card is a fixed 340px, so this block takes whatever is left of it and spreads its rows
   through that: the meters evenly apart, the IN/OUT row on the floor. Without it the rows
   stack at the top and any card shorter than the tallest one ends with a band of empty panel
   under it -- which is what made the balancers look nothing like the main card. */
.srv-bars{display:flex;flex-direction:column;gap:11px;padding:13px 18px 20px 20px;
  flex:1 1 auto;min-height:0;justify-content:space-between}
/* The wrapper exists ONLY so the tab can swap four meters in one write. It must not take
   part in the layout: a flex box of its own would distribute its four rows by one rule while
   the main card distributes its five by another, and the two cards would space differently
   for no reason a reader could see. display:contents hands the meters straight to .srv-bars,
   so every card -- main included -- lays out through the exact same rule. */
.srv-meters{display:contents}
/* A card with a tab strip has one more band above its meters than the main card does, so
   its meters start lower and, in a box of fixed height, end up more tightly spaced. The
   row already separates the two blocks, and it carries its own bottom padding; the padding
   that used to do that job is what gets given back, so the meters begin -- and therefore
   space themselves -- as the main card's do. */
.srv-tabs + .srv-bars{padding-top:0}
/* Close the gap between the last meter and the IN/OUT row -- on the tabbed cards only, so
   the main card is not touched.
   Both halves are needed and they cancel: the negative margin pulls the row up 4px, and the
   4px added to the bottom padding puts the same 4px back into the block's height. Without
   the second half the block would be 4px shorter, space-between would have 4px MORE to give
   away, and all four gaps would grow -- which is how the earlier attempts at tightening
   ended up looking looser. This way exactly one gap changes, by exactly this number. */
.srv-tabs + .srv-bars{padding-bottom:24px}
.srv-tabs + .srv-bars > .meter-io{margin-top:-4px}
.meter{display:grid;grid-template-columns:54px 1fr 44px;align-items:center;gap:12px}
.meter-l{font-size:10.5px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.05em;
  display:inline-flex;align-items:center;gap:6px}
.meter-l svg{width:13px;height:13px;opacity:.7}
.meter-track{height:6px;background:var(--raise-2);border:none;border-radius:999px;overflow:hidden}
.meter-fill{height:100%;width:0;border-radius:999px;background:#10b981;transition:width .6s ease;box-shadow:none}
.meter-fill.warn{background:#f59e0b}
.meter-fill.hot{background:#ef4444}
.meter-v{text-align:right;font-size:12.5px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums}

/* footer */
.srv-foot{display:flex;align-items:center;gap:10px;margin:0;padding:13px 18px 13px 20px;
  border-top:1px solid var(--line-soft);font-size:12px;color:var(--muted);background:rgba(var(--sh),.12)}
.srv-foot .srv-k{order:0;text-transform:none;font-size:11px;letter-spacing:0}
.srv-foot code{font-size:12px}

.srv-install-label{display:flex;justify-content:space-between;align-items:center;margin:0;padding:14px 18px 8px 20px;font-size:12.5px}

/* status note above the placeholder rows (offline / failed cards) */
.state-note{font-size:11.5px;line-height:1.5;padding:11px 18px 11px 20px;border-bottom:1px solid var(--line-soft)}
.state-note-fail{color:var(--t-down);background:rgba(239,68,68,.06)}
.state-note-off{color:var(--text);background:var(--line-soft)}
/* ensure body-fixed isn't centered for these (it's a normal stack) */
.srv-body-fixed{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;padding:0}
.srv-foot{margin-top:auto}
/* installing card: the log fills remaining space and scrolls internally. */
.srv-card.is-installing .srv-body-fixed{padding:0 18px 14px 20px}
.srv-card.is-installing .install-log{flex:1 1 auto;min-height:0;max-height:none;overflow:auto}
.srv-card.is-installing .srv-foot{margin-top:auto}


/* ===== One-pass shine sweep on hover (right -> left, runs once) ===== */
.srv-card .srv-shine{position:absolute;top:0;bottom:0;right:0;width:60%;z-index:3;pointer-events:none;
  border-radius:inherit;transform:translateX(160%) skewX(18deg);
  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--accent) 10%,transparent),rgba(255,255,255,.13),transparent)}
.srv-card:hover .srv-shine{animation:cardShine .55s ease-out 1}
/* Also fire the sweep programmatically (e.g. on each install-log update). */
.srv-card .srv-shine.shine-run{animation:cardShine .6s ease-out 1}
@keyframes cardShine{from{transform:translateX(160%) skewX(18deg)}to{transform:translateX(-220%) skewX(18deg)}}

/* ===== "Add load balancer" card ===== */
.srv-add-card{display:flex;align-items:center;justify-content:center;text-decoration:none;
  min-height:180px;background:transparent;border:2px dashed var(--line);box-shadow:none;
  transition:border-color .2s ease, background .2s ease, transform .2s ease}
.srv-add-card::before{display:none}
.srv-add-card:hover{border-color:var(--primary);background:color-mix(in srgb,var(--accent) 4%,transparent);transform:translateY(-3px)}
.srv-add-inner{display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;padding:24px}
.srv-add-icon{width:48px;height:48px;border-radius:14px;display:grid;place-items:center;
  background:color-mix(in srgb,var(--accent) 10%,transparent);border:1px solid color-mix(in srgb,var(--accent) 25%,transparent);color:var(--primary)}
.srv-add-icon svg{width:24px;height:24px}
.srv-add-card:hover .srv-add-icon{background:color-mix(in srgb,var(--accent) 18%,transparent)}
.srv-add-title{font-size:15px;font-weight:700;color:var(--text)}
.srv-add-sub{font-size:12px;color:var(--muted)}

/* ===== Checking… state (gray, while we confirm liveness) ===== */
.srv-card.is-checking::before{background:linear-gradient(180deg,var(--muted),var(--faint))}
.srv-card.is-checking{border-color:rgba(100,116,139,.22)}
.srv-card.is-checking .srv-status-pill{background:rgba(100,116,139,.12);color:var(--muted);border-color:rgba(100,116,139,.28)}
.srv-card.is-checking .srv-status-pill::before{animation:dotPulse 1.2s ease-in-out infinite}

/* ===== Success celebration when a server turns green while you watch ===== */
@keyframes srvSuccessFlash{
  0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--t-live) 0%,transparent), 0 18px 40px -8px rgba(var(--sh),.55)}
  18%{box-shadow:0 0 0 3px color-mix(in srgb,var(--t-live) 55%,transparent), 0 0 38px 6px color-mix(in srgb,var(--t-live) 45%,transparent), 0 18px 40px -8px rgba(var(--sh),.55)}
  100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--t-live) 0%,transparent), 0 18px 40px -8px rgba(var(--sh),.55)}
}
.srv-card.srv-celebrate{animation:srvSuccessFlash 1.6s ease-out 1}
/* green sweep that crosses the card once */
.srv-card .srv-success-sweep{position:absolute;inset:0;z-index:4;pointer-events:none;border-radius:inherit;
  background:linear-gradient(120deg,transparent 30%,color-mix(in srgb,var(--t-live) 28%,transparent),transparent 70%);
  transform:translateX(-120%);opacity:0}
.srv-card.srv-celebrate .srv-success-sweep{animation:srvSweep 1.1s ease-out 1}
@keyframes srvSweep{0%{transform:translateX(-120%);opacity:1}100%{transform:translateX(120%);opacity:0}}
/* a check-mark badge that pops in the header */
.srv-success-badge{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) scale(.4);
  z-index:6;pointer-events:none;opacity:0;width:64px;height:64px;border-radius:50%;
  display:grid;place-items:center;background:color-mix(in srgb,var(--t-live) 16%,transparent);border:2px solid color-mix(in srgb,var(--t-live) 70%,transparent);
  box-shadow:0 0 28px color-mix(in srgb,var(--t-live) 50%,transparent)}
.srv-success-badge svg{width:34px;height:34px;stroke:#34d399;stroke-width:3;fill:none;
  stroke-linecap:round;stroke-linejoin:round}
.srv-card.srv-celebrate .srv-success-badge{animation:srvBadgePop 1.5s ease-out 1}
@keyframes srvBadgePop{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.3)}
  20%{opacity:1;transform:translate(-50%,-50%) scale(1.05)}
  55%{opacity:1;transform:translate(-50%,-50%) scale(1)}
  100%{opacity:0;transform:translate(-50%,-50%) scale(1)}
}

/* ===== Skeleton / shimmer loading state for the main card ===== */
.srv-card.srv-skeleton{position:relative}
/* dim the real content while loading */
.srv-card.srv-skeleton .srv-rows,
.srv-card.srv-skeleton .srv-bars,
.srv-card.srv-skeleton .srv-foot,
.srv-card.srv-skeleton #srvInstall,
.srv-card.srv-skeleton #srvFail{opacity:.25;filter:blur(.4px)}
/* moving mirror/shimmer sweep across the whole card while loading */
.srv-skel-shimmer{position:absolute;inset:0;z-index:5;pointer-events:none;border-radius:inherit;overflow:hidden}
.srv-skel-shimmer::before{content:"";position:absolute;top:0;bottom:0;left:0;width:55%;
  background:linear-gradient(100deg,transparent,var(--raise-3),color-mix(in srgb,var(--accent) 10%,transparent),transparent);
  transform:translateX(-160%) skewX(-14deg);animation:skelSweep 1.25s ease-in-out infinite}
@keyframes skelSweep{0%{transform:translateX(-160%) skewX(-14deg)}100%{transform:translateX(260%) skewX(-14deg)}}
/* hide shimmer once loaded */
.srv-card:not(.srv-skeleton) .srv-skel-shimmer{display:none}

/* disabled "add" card until the first load completes */
.srv-add-card.is-disabled{pointer-events:none;opacity:.45;filter:grayscale(.3)}
.srv-add-card.is-disabled .srv-add-icon{animation:none}

/* ===== Skeleton rows for the Manage Servers table ===== */
.skel-row td{padding:16px 14px}
.skel-bar{display:block;height:14px;border-radius:7px;
  background:linear-gradient(100deg,var(--raise-1) 20%,var(--raise-3) 40%,var(--raise-1) 60%);
  background-size:200% 100%;animation:skelBar 1.25s ease-in-out infinite}
@keyframes skelBar{0%{background-position:160% 0}100%{background-position:-160% 0}}

/* disabled buttons during initial load */
.btn.is-disabled{pointer-events:none;opacity:.45;filter:grayscale(.3)}

/* ===== Toast notifications (bottom center) ===== */
.sk-toast-wrap{position:fixed;left:50%;bottom:26px;transform:translateX(-50%);
  z-index:9999;display:flex;flex-direction:column;gap:10px;align-items:center;pointer-events:none}
.sk-toast{display:flex;align-items:center;gap:10px;min-width:240px;max-width:90vw;
  padding:12px 16px;border-radius:12px;font-size:13.5px;font-weight:600;color:var(--text);
  background:linear-gradient(180deg,var(--surface)00,var(--panel)),linear-gradient(180deg,var(--line),var(--surface));
  border:1px solid var(--line);box-shadow:var(--shadow-hard),0 0 0 1px rgba(var(--sh),.3);
  opacity:0;transform:translateY(14px) scale(.97);transition:opacity .25s ease, transform .25s ease}
.sk-toast.show{opacity:1;transform:translateY(0) scale(1)}
.sk-toast-ico{display:grid;place-items:center;width:24px;height:24px;border-radius:50%;flex:0 0 auto}
.sk-toast-ico svg{width:16px;height:16px}
.sk-toast.success .sk-toast-ico{background:color-mix(in srgb,var(--t-live) 16%,transparent);color:var(--t-live);border:1px solid color-mix(in srgb,var(--t-live) 40%,transparent)}
.sk-toast.info .sk-toast-ico{background:color-mix(in srgb,var(--accent) 16%,transparent);color:var(--t-info);border:1px solid color-mix(in srgb,var(--accent) 40%,transparent)}

/* ===== Full-screen success celebration overlay ===== */
.sk-celebrate-overlay{position:fixed;inset:0;z-index:10000;pointer-events:none;
  display:grid;place-items:center;opacity:0}
.sk-celebrate-overlay.show{animation:skCelebOverlay 2.2s ease-out forwards}
@keyframes skCelebOverlay{
  0%{opacity:0}
  8%{opacity:1}
  75%{opacity:1}
  100%{opacity:0}
}
/* soft green radial glow that blooms from center */
.sk-celebrate-overlay::before{content:"";position:absolute;inset:0;
  background:radial-gradient(circle at 50% 45%, color-mix(in srgb,var(--t-live) 22%,transparent), color-mix(in srgb,var(--t-live) 6%,transparent) 30%, transparent 60%)}
/* the big check panel */
.sk-celebrate-card{position:relative;display:flex;flex-direction:column;align-items:center;gap:16px;
  padding:34px 44px;border-radius:22px;
  background:linear-gradient(180deg,var(--surface),var(--panel));border:1px solid color-mix(in srgb,var(--t-live) 40%,transparent);
  box-shadow:var(--shadow-hard),0 0 60px color-mix(in srgb,var(--t-live) 25%,transparent);
  transform:scale(.8)}
.sk-celebrate-overlay.show .sk-celebrate-card{animation:skCelebCard 2.2s cubic-bezier(.18,.9,.3,1.2) forwards}
@keyframes skCelebCard{
  0%{transform:scale(.7);opacity:0}
  14%{transform:scale(1.04);opacity:1}
  26%{transform:scale(1)}
  78%{transform:scale(1);opacity:1}
  100%{transform:scale(.96);opacity:0}
}
.sk-celebrate-ring{width:96px;height:96px;border-radius:50%;display:grid;place-items:center;
  background:color-mix(in srgb,var(--t-live) 14%,transparent);border:3px solid color-mix(in srgb,var(--t-live) 70%,transparent);position:relative}
.sk-celebrate-ring::after{content:"";position:absolute;inset:-3px;border-radius:50%;
  border:3px solid color-mix(in srgb,var(--t-live) 50%,transparent)}
.sk-celebrate-overlay.show .sk-celebrate-ring::after{animation:skRing 1.1s ease-out forwards}
@keyframes skRing{0%{transform:scale(1);opacity:.8}100%{transform:scale(1.6);opacity:0}}
.sk-celebrate-ring svg{width:52px;height:52px;stroke:#34d399;stroke-width:3.2;fill:none;
  stroke-linecap:round;stroke-linejoin:round;
  stroke-dasharray:40;stroke-dashoffset:40}
.sk-celebrate-overlay.show .sk-celebrate-ring svg{animation:skCheck .5s .18s ease-out forwards}
@keyframes skCheck{to{stroke-dashoffset:0}}
.sk-celebrate-title{font-size:19px;font-weight:800;color:var(--text);letter-spacing:-.01em}
.sk-celebrate-sub{font-size:13px;color:var(--muted);margin-top:-8px}
/* confetti dots */
.sk-confetti{position:absolute;top:46%;left:50%;width:10px;height:10px;border-radius:2px;opacity:0}
.sk-celebrate-overlay.show .sk-confetti{animation:skConfetti 1.5s ease-out forwards}
@keyframes skConfetti{
  0%{opacity:1;transform:translate(0,0) rotate(0)}
  100%{opacity:0;transform:translate(var(--cx),var(--cy)) rotate(var(--cr))}
}

/* ===== Themed confirm/alert dialog (skConfirm / skAlert) ===== */
.sk-overlay{position:fixed;inset:0;z-index:9998;display:none;align-items:center;justify-content:center;
  background:rgba(4,8,14,.62);backdrop-filter:blur(3px);padding:20px}
.sk-overlay.open{display:flex;animation:skFade .18s ease}
@keyframes skFade{from{opacity:0}to{opacity:1}}
.sk-box{width:100%;max-width:420px;background:linear-gradient(180deg,var(--surface),var(--panel));
  border:1px solid var(--line);border-radius:16px;padding:26px 24px 20px;text-align:center;
  box-shadow:var(--shadow-hard);animation:skPop .2s cubic-bezier(.18,.9,.3,1.15)}
@keyframes skPop{from{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}
.sk-icon{width:56px;height:56px;margin:0 auto 14px;border-radius:50%;display:grid;place-items:center}
.sk-icon svg{width:28px;height:28px}
.sk-icon.sk-danger{background:rgba(239,68,68,.14);color:var(--t-down);border:1px solid rgba(239,68,68,.4)}
.sk-icon.sk-info{background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--t-info);border:1px solid color-mix(in srgb,var(--accent) 40%,transparent)}
.sk-title{font-size:17px;font-weight:800;color:var(--text);margin:0 0 8px}
.sk-text{font-size:13.5px;line-height:1.6;color:var(--muted);margin:0 0 20px}
/* A message a machine wrote. Left to right and left aligned whatever the page does around it:
   ffmpeg speaks English in whole lines, and centred it reads like nothing at all. Its own line
   breaks are kept, and it scrolls inside the dialog rather than pushing the OK button off the
   screen. `isolate` stops one Arabic word in a path from reversing the line around it. */
.sk-box.sk-wide{max-width:600px}
.sk-text.sk-log{direction:ltr;text-align:left;unicode-bidi:isolate;white-space:pre-wrap;
  overflow-wrap:anywhere;font-family:var(--mono);font-size:12px;line-height:1.7;
  color:var(--text);background:var(--inset);border:1px solid var(--line-soft);
  border-radius:10px;padding:11px 13px;max-height:240px;overflow:auto;margin:0 0 18px}
.sk-log .lg-tag{color:var(--muted)}          /* who spoke: [out#0/matroska] */
.sk-log .lg-num{color:var(--t-info)}         /* codes, sizes, counts */
.sk-log .lg-err{color:var(--t-down);font-weight:700}
.sk-log .lg-str{color:var(--t-warn)}         /* paths, URLs, quoted names */
.sk-actions{display:flex;gap:10px;justify-content:center}
.sk-actions .btn{min-width:110px;justify-content:center}

/* ===== Manage Streams ===== */
.stream-toolbar{display:flex;align-items:center;gap:12px;padding:9px 16px;border-bottom:1px solid var(--line)}
.stream-search{position:relative;flex:1;max-width:340px}
.stream-search svg{position:absolute;left:11px;top:50%;transform:translateY(-50%);width:15px;height:15px;color:var(--muted)}
.stream-search input{width:100%;padding:9px 12px 9px 34px;background:var(--inset);border:1px solid var(--line);border-radius:9px;color:var(--text);font-size:13px}
.stream-toolbar select{padding:9px 12px;background:var(--inset);border:1px solid var(--line);border-radius:9px;color:var(--text);font-size:13px}
.stream-count{margin-left:auto;font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}
.ch-logo{display:inline-grid;place-items:center;width:32px;height:32px;border-radius:7px;overflow:hidden;background:var(--inset);border:1px solid var(--line-soft)}
.ch-logo img{width:100%;height:100%;object-fit:contain}
.ch-logo-fallback{position:relative}
.ch-logo-fallback::after{content:"";position:absolute;inset:0;background:
  radial-gradient(circle at 50% 40%, color-mix(in srgb,var(--accent) 25%,transparent), transparent 60%);}
.ch-logo-fallback::before{content:"TV";position:absolute;inset:0;display:grid;place-items:center;font-size:9px;font-weight:800;color:var(--muted);letter-spacing:.5px}
.ch-url{max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted);font-size:11.5px}
.ch-actions{white-space:nowrap;text-align:right}
.st-badge.st-live{background:color-mix(in srgb,var(--t-live) 14%,transparent);color:var(--t-live);border:1px solid color-mix(in srgb,var(--t-live) 35%,transparent)}
.st-badge.st-vod{background:rgba(167,139,250,.14);color:var(--t-violet);border:1px solid rgba(167,139,250,.35)}
.modal .modal-body textarea{width:100%;background:var(--inset);border:1px solid var(--line);border-radius:9px;color:var(--text);
  padding:11px 12px;font-size:12px;line-height:1.6;resize:vertical;min-height:150px}

/* ===== Manage Lines extras ===== */
.field{position:relative}
.mini-gen{position:absolute;right:8px;top:31px;font-size:10.5px;padding:3px 8px;border-radius:6px;
  background:var(--line);border:1px solid var(--line);color:var(--muted);cursor:pointer}
.mini-gen:hover{color:var(--text);border-color:var(--primary)}
.exp-past{color:var(--t-down)}

/* ===== Streams Status ===== */
.ss-auto{display:flex;align-items:center;gap:6px;font-size:12.5px;color:var(--muted);cursor:pointer}
.ss-auto input{width:auto}
.ss-foot{padding:8px 16px;font-size:11.5px;border-top:1px solid var(--line)}
.ss-foot:empty{display:none}
.dot-idle{background:var(--faint) !important}
.st-badge.st-enc{background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--t-info);border:1px solid color-mix(in srgb,var(--accent) 35%,transparent)}

/* Manage Streams: live-row accent */
.srv-table tr.row-ok td:first-child{box-shadow:inset 3px 0 0 var(--live,#34d399)}
.srv-table tr.row-idle td:first-child{box-shadow:inset 3px 0 0 var(--faint)}
.srv-table tr.row-down td:first-child{box-shadow:inset 3px 0 0 var(--down,#fb7185)}

/* Manage Streams: type tabs + CPU text coloring */
.ch-tabs{display:flex;gap:4px;padding:12px 16px 0}
.ch-tab{background:transparent;border:none;border-bottom:2px solid transparent;color:var(--muted);
  font-size:13px;font-weight:600;padding:8px 4px;margin-right:18px;cursor:pointer;display:flex;align-items:center;gap:7px}
.ch-tab:hover{color:var(--text)}
.ch-tab.is-active{color:var(--text);border-bottom-color:var(--primary)}
.ch-tab-count{background:var(--line);border-radius:999px;padding:1px 8px;font-size:11px;font-variant-numeric:tabular-nums}
.ch-tab.is-active .ch-tab-count{background:color-mix(in srgb,var(--accent) 18%,transparent);color:var(--t-info)}
.cpu-warn{color:var(--t-warn)}
.cpu-hot{color:var(--t-down)}
.ch-actions .btn[disabled]{opacity:.35;cursor:not-allowed}

/* Manage Streams: Advanced section toggle */
.ch-advanced-toggle{display:flex;align-items:center;gap:6px;background:transparent;border:none;
  color:var(--muted);font-size:12.5px;font-weight:600;padding:6px 0;margin:2px 0 10px;cursor:pointer}
.ch-advanced-toggle:hover{color:var(--text)}

/* ===== Settings page ===== */
.settings-shell{display:flex;gap:20px;align-items:flex-start}
.settings-nav{flex:0 0 240px;background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:10px;
  display:flex;flex-direction:column;gap:2px;position:sticky;top:20px}
.settings-nav-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:9px;background:transparent;
  border:none;color:var(--muted);font-size:13px;font-weight:600;text-align:left;cursor:pointer;width:100%}
.settings-nav-item svg{width:16px;height:16px;flex:0 0 auto}
.settings-nav-item:hover{color:var(--text);background:var(--surface)}
.settings-nav-item.is-active{color:var(--t-info);background:color-mix(in srgb,var(--accent) 12%,transparent)}
.settings-content{flex:1;min-width:0;background:var(--panel);border:1px solid var(--line);border-radius:14px;overflow:hidden}
.settings-pane{padding:22px 26px 8px}
.settings-pane-head{margin-bottom:18px}
.settings-pane-head h3{margin:0 0 4px;font-size:15px;font-weight:700;color:var(--text)}
.settings-pane-head p{margin:0;font-size:12.5px}
.settings-group{border:1px solid var(--line-soft,var(--line));border-radius:12px;overflow:hidden}
.settings-row{display:flex;align-items:center;gap:20px;justify-content:space-between;padding:16px 18px;border-bottom:1px solid var(--line-soft,var(--line))}
.settings-row-last{border-bottom:none}
.settings-row-label{flex:1;min-width:0}
.settings-row-title{font-size:13.5px;font-weight:600;color:var(--text)}
.settings-row-desc{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.5}
.settings-row-control{flex:0 0 auto;width:260px;display:flex;align-items:center;gap:8px}
.settings-row-control-sm{width:140px}
.settings-row-control input{width:100%;padding:9px 12px;background:var(--inset);border:1px solid var(--line);border-radius:9px;color:var(--text);font-size:13px}
.settings-suffix{font-size:11.5px;color:var(--muted);white-space:nowrap}
.settings-note{margin:16px 0 18px;font-size:11.5px}
.settings-skel{display:flex;flex-direction:column;gap:12px;margin-bottom:18px}
.settings-skel .skel-bar{height:56px;border-radius:12px}
.settings-footbar{padding:14px 26px;border-top:1px solid var(--line);display:flex;justify-content:flex-end}
@media (max-width: 860px){
  .settings-shell{flex-direction:column}
  .settings-nav{flex:none;width:100%;position:static;flex-direction:row;overflow-x:auto}
  .settings-row{flex-direction:column;align-items:flex-start;gap:10px}
  .settings-row-control,.settings-row-control-sm{width:100%}
}

/* Manage Users: HLS/TS copy buttons */
.ln-copy-cell{white-space:nowrap}
.ln-copy-cell .btn{min-width:44px;text-align:center}

/* Install log wrapper + copy button */
.log-wrap{position:relative;display:flex;flex-direction:column;min-height:0;flex:1 1 auto}
.log-copy{position:absolute;top:8px;right:8px;z-index:3;width:30px;height:30px;display:grid;place-items:center;
  border-radius:8px;background:rgba(15,23,36,.82);border:1px solid var(--line);color:var(--muted);cursor:pointer;
  backdrop-filter:blur(3px);transition:color .15s,border-color .15s,background .15s}
.log-copy:hover{color:#fff;border-color:var(--primary);background:color-mix(in srgb,var(--accent) 16%,transparent)}
.log-copy svg{width:14px;height:14px}

/* Install progress row (bar + percentage) — kept from shrinking in the flex card */
.inst-prog-row{display:flex;align-items:center;gap:10px;flex:0 0 auto;margin:14px 0 10px}
.inst-prog-row .progress-wrap{flex:1 1 auto}
.inst-prog-pct{flex:0 0 auto;min-width:38px;text-align:right;font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}
.srv-card.is-installing .inst-prog-row{margin-top:14px}

/* Compact AES tag in the streams table — always shown; green when enabled,
   translucent grey when disabled. Small so it never enlarges the row. */
.aes-tag{display:inline-block;vertical-align:middle;margin-left:6px;font-size:9px;font-weight:700;
  letter-spacing:.4px;line-height:1;padding:3px 5px;border-radius:5px;
  color:rgba(148,163,184,.55);background:rgba(148,163,184,.10);border:1px solid rgba(148,163,184,.18)}
.aes-tag.aes-on{color:var(--t-live);background:color-mix(in srgb,var(--t-live) 14%,transparent);border-color:color-mix(in srgb,var(--t-live) 35%,transparent)}

/* Manage Users: allowed-output-type permission checkboxes */
.ln-perm-row{display:flex;gap:20px;margin-top:6px}
.ln-perm{display:flex;align-items:center;gap:7px;font-size:13px;cursor:pointer;margin:0}
.ln-perm input{width:auto;flex:0 0 auto;margin:0}
.ln-copy-cell .btn[disabled]{opacity:.35;cursor:not-allowed}

/* Download playlist modal */
.dl-typeseg{display:flex;gap:8px;margin-top:6px}
.dl-type{flex:1;padding:10px;border-radius:9px;border:1px solid var(--line);background:var(--inset);color:var(--muted);
  font-size:13px;font-weight:600;cursor:pointer;transition:all .15s}
.dl-type:hover:not(:disabled){color:var(--text);border-color:var(--line-strong)}
.dl-type.is-active{color:var(--accent-ink);font-weight:700;
  border-color:color-mix(in srgb,var(--accent) 55%,transparent);
  background:color-mix(in srgb,var(--accent) 12%,transparent)}
.dl-type:disabled,.dl-type.is-off{opacity:.35;cursor:not-allowed}
.dl-urlrow{display:flex;gap:8px;align-items:center;margin-top:6px}
.dl-urlrow input{flex:1;min-width:0}

/* ===== Stream modal: professional toggle switches ===== */
.ch-toggles{display:flex;flex-direction:column;gap:2px;margin:16px 0 4px;
  border:1px solid var(--line);border-radius:12px;overflow:hidden;background:var(--inset)}
.stream-switch{display:flex;align-items:center;gap:13px;padding:13px 15px;cursor:pointer;margin:0;
  border-bottom:1px solid var(--line-soft,rgba(148,163,184,.10))}
.ch-toggles .stream-switch:last-child{border-bottom:none}
.stream-switch:hover{background:var(--line-soft)}
.stream-switch input{position:absolute;opacity:0;width:0;height:0;pointer-events:none}
.stream-switch-track{flex:0 0 auto;width:40px;height:23px;border-radius:999px;background:var(--line);
  border:1px solid var(--line);position:relative;transition:background .18s,border-color .18s}
.stream-switch-thumb{position:absolute;top:2px;left:2px;width:17px;height:17px;border-radius:50%;
  background:var(--muted);transition:transform .18s,background .18s;box-shadow:var(--shadow-soft)}
.stream-switch input:checked + .stream-switch-track{background:color-mix(in srgb,var(--t-live) 30%,transparent);border-color:color-mix(in srgb,var(--t-live) 60%,transparent)}
.stream-switch input:checked + .stream-switch-track .stream-switch-thumb{transform:translateX(17px);background:var(--t-live)}
.stream-switch input:focus-visible + .stream-switch-track{box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 30%,transparent)}
.stream-switch-label{display:flex;flex-direction:column;gap:2px;line-height:1.35}
.stream-switch-label b{font-size:13px;font-weight:600;color:var(--text)}
.stream-switch-label .faint{font-size:11.5px}
/* Advanced section separator */
.ch-adv-sep{display:flex;align-items:center;gap:10px;margin:16px 0 12px;color:var(--muted);
  font-size:11px;font-weight:700;letter-spacing:.6px;text-transform:uppercase}
.ch-adv-sep::before,.ch-adv-sep::after{content:"";flex:1;height:1px;background:var(--line)}

/* ===== Expandable stream row drawer ===== */
.ch-row{cursor:pointer}
.ch-row.is-expanded{background:color-mix(in srgb,var(--accent) 6%,transparent)}
.ch-row.is-expanded td{border-bottom-color:transparent}
.ch-drawer-row > td{padding:0 !important;background:var(--inset);
  border-bottom:1px solid var(--line);box-shadow:inset var(--shadow-hard)}
.ch-drawer{padding:16px 18px;animation:chDrawerIn .18s ease-out}
@keyframes chDrawerIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
.ch-drawer-grid{display:grid;grid-template-columns:260px 1fr;gap:18px;align-items:start}
.ch-drawer-thumb{border-radius:10px;overflow:hidden;border:1px solid var(--line);background:var(--deep);
  aspect-ratio:16/9;display:flex;align-items:center;justify-content:center}
.ch-drawer-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.ch-drawer-thumb-empty{color:var(--muted);font-size:12px}
.ch-drawer-info{display:grid;grid-template-columns:1fr 1fr;gap:8px 22px}
.ch-dk{display:flex;flex-direction:column;gap:2px;padding:7px 0;border-bottom:1px solid var(--line-soft)}
.ch-dk span{font-size:10.5px;letter-spacing:.4px;text-transform:uppercase;color:var(--muted)}
.ch-dk b{font-size:13px;color:var(--text);font-weight:600}
@media (max-width:720px){.ch-drawer-grid{grid-template-columns:1fr}.ch-drawer-info{grid-template-columns:1fr}}

/* Play button over the drawer thumbnail */
.ch-drawer-thumb{position:relative}
.stream-play-btn{position:absolute;inset:0;margin:auto;width:64px;height:64px;
  display:flex;align-items:center;justify-content:center;cursor:pointer;border:none;
  background:transparent;color:rgba(255,255,255,.82);padding:0;
  transition:transform .18s cubic-bezier(.34,1.56,.64,1),filter .18s ease,color .18s ease}
.stream-play-btn svg{width:64px;height:64px;margin:0;
  filter:drop-shadow(0 3px 6px rgba(var(--sh),.55)) drop-shadow(0 0 1px rgba(var(--sh),.4))}
.stream-play-btn:hover{transform:scale(1.1);color:rgba(255,255,255,.98);filter:drop-shadow(0 0 10px rgba(255,255,255,.5))}
.stream-play-btn:active{transform:scale(1.02)}
.stream-play-btn.is-disabled,.stream-play-btn:disabled{color:rgba(255,255,255,.32);cursor:not-allowed}
.stream-play-btn.is-disabled:hover,.stream-play-btn:disabled:hover{transform:none;color:rgba(255,255,255,.32);
  filter:drop-shadow(0 2px 4px rgba(var(--sh),.5))}

/* Live preview player modal — sizes to the video, capped to the viewport */
/* Small on purpose, everywhere. This was min(1100px,94vw) with a 78vh video inside it, so
   on a phone the preview filled the screen edge to edge and there was no way to see the
   list it was launched from. A preview answers "is this stream alive and is it the right
   channel" -- that does not need a cinema, and the operator has a real player for when it
   does. */
.pv-modal{max-width:min(560px, calc(100vw - 24px));width:auto;padding:0}
.pv-body{padding:0}
.pv-stage{position:relative;background:#000;border-radius:0 0 14px 14px;overflow:hidden;
  display:flex;align-items:center;justify-content:center;min-height:220px}
.pv-stage video{display:block;max-width:100%;max-height:min(46vh, 320px);width:auto;height:auto;
  background:#000}
.pv-loading{position:absolute;inset:0;display:flex;flex-direction:column;gap:12px;
  align-items:center;justify-content:center;color:var(--text);font-size:13px;background:rgba(var(--sh),.4)}
.pv-spinner{width:34px;height:34px;border-radius:50%;border:3px solid rgba(255,255,255,.2);
  border-top-color:var(--accent);animation:spin .8s linear infinite}

/* ===== Streams table: name icons, type badge, icon action buttons ===== */
.ch-name-cell{display:flex;flex-direction:column;align-items:flex-start;gap:5px}
.ch-name-cell .ch-name-txt{font-weight:600;font-size:14.5px}
.ch-name-tags{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.ch-tag{display:inline-flex;align-items:center;gap:4px;height:19px;padding:0 7px;
  border-radius:5px;font-size:9px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:rgba(148,163,184,.75);background:var(--line-soft);
  border:1px solid rgba(148,163,184,.18);flex:0 0 auto;transition:all .15s}
.ch-tag svg{width:10.5px;height:10.5px}
/* All grey; state is shown by the icon (✓ enabled / ✗ disabled), not colour. */
.ch-tag.on{color:var(--text);background:rgba(148,163,184,.12);border-color:rgba(148,163,184,.28)}
.ch-tag.on svg{color:#8fa3bf}
.ch-tag.off{color:rgba(148,163,184,.5);background:var(--line-soft);border-color:rgba(148,163,184,.12)}
.ch-tag.off svg{color:rgba(148,163,184,.45)}

/* Type badge (reference styling) */
.type-badge{display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;
  font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;border:1px solid transparent}
.type-badge.stream{color:var(--muted);background:rgba(148,163,184,.1);border-color:rgba(148,163,184,.22)}
.type-badge.od{color:#b3a1e0;background:rgba(167,139,250,.1);border-color:rgba(167,139,250,.24)}

/* Icon action buttons (reference styling) */
.ch-actions{white-space:nowrap;display:flex;gap:6px;justify-content:flex-end}
.ic-btn{width:30px;height:30px;border-radius:8px;border:1px solid var(--line);
  background:var(--raise-1);color:var(--muted);cursor:pointer;display:inline-flex;
  align-items:center;justify-content:center;transition:all .12s;padding:0}
.ic-btn svg{width:15px;height:15px}
.ic-btn:hover{color:var(--accent);border-color:color-mix(in srgb,var(--accent) 55%,transparent);
  background:color-mix(in srgb,var(--accent) 12%,transparent)}
.ic-btn.warn:hover{color:var(--t-warn);border-color:#f59e0b}
.ic-btn.danger:hover{color:#ef4444;border-color:#ef4444}
.ic-btn.primary:hover{color:var(--primary);border-color:var(--primary)}
.ic-btn.del:hover{color:#ef4444;border-color:#ef4444}
.ic-btn:disabled{opacity:.35;cursor:not-allowed}
.ic-btn:disabled:hover{color:var(--muted);border-color:var(--line)}

/* Player: thumbnail backdrop before playback starts */
.pv-stage.has-poster{background-size:cover;background-position:center;background-repeat:no-repeat}
.pv-stage.has-poster::before{content:"";position:absolute;inset:0;background:rgba(3,6,10,.55);
  backdrop-filter:blur(2px)}
.pv-stage.has-poster video{position:relative;z-index:1}
.pv-stage .pv-loading{z-index:2}

/* ===== Responsive streams table — drop non-essential columns as width shrinks.
   Full details always remain available by expanding the row (drawer). ===== */
@media (max-width:1100px){ .col-br{display:none} }
@media (max-width:960px){ .col-cpu,.col-ram,.col-fps,.col-spd{display:none} }
@media (max-width:760px){ .col-res{display:none} }
@media (max-width:560px){ .col-type{display:none} }
@media (max-width:460px){ .col-up{display:none} }
/* Tighten table padding on small screens so what remains fits without scroll. */
@media (max-width:760px){
  .srv-table th,.srv-table td{padding-left:10px;padding-right:10px}
  .ch-actions{gap:4px}
  .ic-btn{width:28px;height:28px}
  .ch-drawer-grid{grid-template-columns:1fr}
}
@media (max-width:460px){
  .srv-table th,.srv-table td{padding-left:7px;padding-right:7px;font-size:12px}
  .ch-name-cell{gap:4px}
}

/* Center the metric columns (Type, Uptime, Resolution, Bitrate, CPU, RAM) */
.srv-table th.col-type,.srv-table td.col-type,
.srv-table th.col-up,.srv-table td.col-up,
.srv-table th.col-res,.srv-table td.col-res,
.srv-table th.col-br,.srv-table td.col-br,
.srv-table th.col-cpu,.srv-table td.col-cpu,
.srv-table th.col-ram,.srv-table td.col-ram,
.srv-table th.col-fps,.srv-table td.col-fps,
.srv-table th.col-spd,.srv-table td.col-spd{text-align:center}
/* Uptime is updated live by JS; make sure it stays centered regardless. */
#streamBody td.col-up,#streamBody td[id^="up_"]{text-align:center !important}
/* header cells render an icon+label inline element — center it too */
.srv-table th.col-type > *,.srv-table th.col-up > *,.srv-table th.col-res > *,
.srv-table th.col-br > *,.srv-table th.col-cpu > *,.srv-table th.col-ram > *,
.srv-table th.col-fps > *,.srv-table th.col-spd > *{justify-content:center}
/* meter cells (cpu/ram/bitrate) are flex — center their contents */
.srv-table td.col-cpu .cell-meter,.srv-table td.col-ram .cell-meter,
.srv-table td.col-br .cell-meter{justify-content:center;margin:0 auto}
.srv-table td.col-type .type-badge{margin:0 auto}

/* Status as an icon-only dot (saves horizontal space) */
.srv-table th:first-child,.srv-table td:first-child{width:46px;text-align:center;padding-left:8px;padding-right:8px}
.st-dot{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px}
.st-dot .dot{width:11px;height:11px}
/* The st-ok/st-off/st-fail classes also carry the old badge background+border;
   neutralize them here so only the glowing dot shows (no square). */
.st-dot.st-ok,.st-dot.st-off,.st-dot.st-fail,.st-dot.st-inst{background:none;border:none;box-shadow:none}
/* Slightly smaller stream name */
.ch-name-cell .ch-name-txt{font-size:13px}

/* ===== Generic centered cells + responsive hiding (Servers & Users tables) ===== */
.srv-table th.c-center,.srv-table td.c-center{text-align:center}
.srv-table th.c-center .th-ico{justify-content:center}
.srv-table td.c-center .cell-meter,.srv-table td.c-center .meter{justify-content:center;margin:0 auto}
.srv-table td.c-center .dl-btn,.srv-table td.c-center .btn{margin:0 auto}

/* Manage Servers — drop least-critical columns as the viewport narrows.
   All details remain in the server's own card on the dashboard. */
@media (max-width:1180px){ .col-disk{display:none} }
@media (max-width:1040px){ .col-os{display:none} }
@media (max-width:920px){ .col-uptime{display:none} }
@media (max-width:780px){ .col-cpu,.col-ram,.col-fps,.col-spd{display:none} }
@media (max-width:620px){ .col-port{display:none} }
@media (max-width:500px){ .col-country{display:none} }

/* Manage Users — drop password/expiry first (still editable via Edit). */
@media (max-width:680px){ .col-exp{display:none} }
@media (max-width:520px){ .col-pass{display:none} }

/* Small-screen table padding so remaining columns fit without horizontal scroll */
@media (max-width:760px){
  .srv-table th,.srv-table td{padding-left:10px;padding-right:10px}
}
@media (max-width:460px){
  .srv-table th,.srv-table td{padding-left:7px;padding-right:7px;font-size:12px}
}

/* Server status dot (installing variant) */
.st-dot.st-inst{background:none}

/* Source column: small italic, muted text */
.src-txt{font-style:italic;font-size:9.5px;color:var(--muted,var(--muted));font-family:var(--mono,monospace);letter-spacing:.01em;white-space:nowrap}

/* ===== Global thin, transparent scrollbars (match the install-log style)
   Applied to the whole page and every scrollable region across all pages. ===== */
*{scrollbar-width:thin;scrollbar-color:rgba(148,163,184,.35) transparent}
*::-webkit-scrollbar{width:8px;height:8px}
*::-webkit-scrollbar-track{background:transparent}
*::-webkit-scrollbar-thumb{background:rgba(148,163,184,.28);border-radius:8px}
*::-webkit-scrollbar-thumb:hover{background:rgba(148,163,184,.5)}
*::-webkit-scrollbar-corner{background:transparent}
html,body{scrollbar-width:thin;scrollbar-color:rgba(148,163,184,.35) transparent}

/* NET split into IN (left) + OUT (right) — two equal half-width meters */
.meter-io{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:center;
  margin-top:3px;margin-bottom:2px;padding-top:11px;border-top:1px solid var(--line-soft,rgba(148,163,184,.1))}
.io-cell{display:flex;align-items:center;justify-content:space-between;gap:8px;
  padding:7px 12px;background:var(--raise-1);border:1px solid var(--line-soft,rgba(148,163,184,.12));
  border-radius:8px;min-width:0}
.io-l{display:inline-flex;align-items:center;gap:6px;font-size:10.5px;font-weight:600;
  text-transform:uppercase;letter-spacing:.05em;color:var(--muted);flex:0 0 auto}
.io-l svg{width:15px;height:15px;opacity:.75}
.io-v{font-size:13px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}

/* ── Per-stream server picker (Add/Edit stream modal) ───────────────────── */
.stream-servers-field{margin-top:2px}
.req{color:var(--t-down)}
.stream-servers-list{display:flex;flex-direction:column;max-height:230px;overflow:auto;
  border:1px solid var(--line,var(--line));border-radius:12px;background:var(--inset)}
.stream-srv-item{display:flex;align-items:center;gap:10px;padding:9px 11px;border-radius:9px;cursor:pointer;
  background:var(--raise-1);border:1px solid var(--line,var(--line));
  transition:border-color .14s,background .14s}
.stream-srv-item:hover{border-color:color-mix(in srgb,var(--accent) 35%,transparent)}
.stream-srv-item.sel{border-color:color-mix(in srgb,var(--accent) 50%,transparent);background:color-mix(in srgb,var(--accent) 6%,transparent)}
.stream-srv-item input{margin:0;width:16px;height:16px;accent-color:var(--accent);flex:none;cursor:pointer}
.stream-srv-dot{width:8px;height:8px;border-radius:50%;flex:none;background:#5a6b84}
.stream-srv-dot.up{background:var(--t-live)}
.stream-srv-dot.down{background:#f87171}
.stream-srv-name{font-size:13px;font-weight:600;color:var(--text);flex:1;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.stream-srv-badge{font-size:9.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  padding:2px 7px;border-radius:5px;flex:none}
.stream-srv-badge.main{background:rgba(52,211,153,.14);color:var(--t-live)}
.stream-srv-badge.lb{background:rgba(148,163,184,.14);color:var(--muted)}
.stream-servers-empty{font-size:12.5px;color:var(--faint);padding:8px 2px}

/* ── Server picker cards (Run on servers) — switch-styled like the toggles ── */
.srv-switch{display:flex;align-items:flex-start;gap:13px;padding:12px 14px;cursor:pointer;margin:0;
  border-bottom:1px solid var(--line-soft,rgba(148,163,184,.10));transition:background .14s}
.stream-servers-list .srv-switch:last-child{border-bottom:none}
.srv-switch:hover{background:var(--line-soft)}
.srv-switch input{position:absolute;opacity:0;width:0;height:0;pointer-events:none}
.srv-switch .stream-switch-track{align-self:center}
.srv-switch input:checked + .stream-switch-track{
  background:color-mix(in srgb,var(--t-live) 32%,transparent);
  border-color:color-mix(in srgb,var(--t-live) 65%,transparent)}
.srv-switch input:checked + .stream-switch-track .stream-switch-thumb{transform:translateX(17px);background:var(--t-live)}
.srv-switch input:focus-visible + .stream-switch-track{box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 30%,transparent)}
.srv-switch-main{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}
.srv-switch-top{display:flex;align-items:center;gap:8px;min-width:0}
.srv-switch-top .fi{flex:none;width:21px;height:15px;border-radius:2px;box-shadow:var(--shadow-soft)}
.srv-switch-top b{font-size:13px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.srv-switch-badge{margin-left:auto;flex:none}
.srv-switch-ip{white-space:nowrap;font-size:11.5px;color:var(--muted);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.srv-badge-right{margin-left:auto}
/* Per-server metrics — resources | network (no boxes), value then label */
.srv-switch-stats{display:flex;flex-wrap:wrap;align-items:center;gap:5px 0;margin-top:6px;font-size:11.5px}
.srv-grp{display:inline-flex;align-items:center;flex-wrap:wrap}
.srv-m{display:inline-flex;align-items:baseline;white-space:nowrap;margin-right:12px}
.srv-grp .srv-m:last-child{margin-right:0}
.srv-m-v{color:var(--text);font-weight:600;font-variant-numeric:tabular-nums}
.srv-m-k{color:var(--faint);font-weight:600;margin-left:4px}
.srv-div{width:1px;align-self:stretch;min-height:13px;background:var(--line);margin:0 12px}
.srv-net-a{font-weight:700;margin-right:3px}
.srv-net-a.in{color:#5dcaa5}
.srv-net-a.out{color:#7fb0e8}
/* "Run on servers" header row with Select-all / Deselect-all */
.srv-field-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px}
.srv-field-actions{display:flex;gap:6px;flex:none}
.srv-mini-btn{font-size:11px;font-weight:600;color:var(--muted);background:transparent;font-family:inherit;
  border:1px solid var(--line,var(--line));border-radius:7px;padding:4px 9px;cursor:pointer;
  transition:color .14s,border-color .14s,background .14s}
.srv-mini-btn:hover{color:var(--t-info);border-color:color-mix(in srgb,var(--accent) 40%,transparent);background:color-mix(in srgb,var(--accent) 6%,transparent)}

/* ── Per-stream server list under the name (Manage Streams) ─────────────── */
.ch-srv-list{display:flex;flex-direction:column;gap:3px;margin-top:7px}
.ch-srv{display:flex;align-items:center;gap:7px;font-size:11.5px;max-width:280px}
.ch-srv-dot{width:7px;height:7px;flex:none}
.ch-srv-name{color:var(--muted);font-weight:600;display:inline-flex;align-items:center;gap:6px;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ch-srv-kind{flex:none;font-size:8.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  padding:1px 5px;border-radius:4px}
.ch-srv-kind.main{background:rgba(52,211,153,.14);color:var(--t-live)}
.ch-srv-kind.lb{background:rgba(148,163,184,.14);color:var(--muted)}
.ch-srv-up{margin-left:auto;flex:none;color:var(--muted);font-size:11px;font-variant-numeric:tabular-nums}
.dot-pending{background:#f5b544 !important;box-shadow:0 0 8px rgba(245,181,68,.55)}

/* ---- Sidebar footer + account menu -------------------------------------------------------
   NOTE on variables: this sheet defines --text for foreground and --surface/--panel for raised
   backgrounds. There is no --card, and --ink is a very dark navy (var(--ink)) used for text ON
   bright fills -- using it for body text made the version label invisible, and --card resolved
   to nothing so the dropdown rendered fully transparent. ---- */
/* Account menu header: version + licence. This lives in the dropdown rather than pinned to the
   bottom of the sidebar -- it is reference information, not navigation, and a permanent block
   down there ate vertical space while competing with the menu itself. */
.acct-head{padding:11px 13px 10px}
.acct-head-top{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:5px}
.acct-ver{font-family:var(--mono);font-size:11px;font-weight:600;color:var(--text)}
/* A bordered pill, so the state carries its own shape and not just a colour -- and every
   colour here comes from currentColor, because this same element is tinted --warn when an
   update is waiting and --violet on dev. A hard-coded --live border would have lied on both. */
.acct-ok{display:inline-flex;align-items:center;gap:5px;color:var(--live);font-size:9.5px;
  font-weight:700;letter-spacing:.05em;text-transform:uppercase;white-space:nowrap;
  padding:3px 8px;border-radius:999px;
  border:1px solid color-mix(in srgb,currentColor 38%,transparent);
  background:color-mix(in srgb,currentColor 12%,transparent)}
.acct-dot{width:5px;height:5px;border-radius:50%;background:currentColor;flex:0 0 auto}
.acct-head-exp{color:var(--faint);font-size:10.5px}

.acct-wrap{position:relative}
/* Same box as .thm-btn beside it: the two topbar icons read as one family, not a square
   and a circle that happen to be neighbours. */
.acct-btn{width:34px;height:34px;border-radius:10px;display:inline-flex;align-items:center;
  justify-content:center;border:1px solid var(--line);background:var(--input);color:var(--muted);
  cursor:pointer;transition:all .13s;padding:0}
.acct-btn svg{width:17px;height:17px}
.acct-btn:hover,.acct-btn.open{color:var(--accent);border-color:color-mix(in srgb,var(--accent) 55%,transparent);
  background:color-mix(in srgb,var(--accent) 12%,transparent)}
/* min-width WITHOUT a max-width let the card grow to whatever the name was -- a long
   username pushed the menu halfway across the topbar instead of being cut. The ellipsis
   rules on .acct-who-n only ever fire once something bounds the width. */
.acct-menu{position:absolute;top:calc(100% + 9px);right:0;min-width:224px;max-width:288px;z-index:200;
  background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:6px;
  box-shadow:var(--shadow-hard);display:none}
.acct-menu.open{display:block}
.acct-item{display:flex;align-items:center;gap:10px;padding:9px 11px;border-radius:8px;
  color:var(--text);text-decoration:none;font-size:13px;transition:background .12s}
.acct-item svg{width:15px;height:15px;color:var(--muted);flex:0 0 auto}
.acct-item:hover{background:var(--raise-2)}
.acct-item.danger{color:var(--down)}
.acct-item.danger svg{color:var(--down)}
.acct-item.danger:hover{background:rgba(251,113,133,.12)}
/* --line-soft is nearly invisible against --surface, so the separator that was already here
   never actually read as a divider. --line has the contrast the menu needs. */
.acct-sep{height:1px;background:var(--line);margin:6px 4px}

.page-head-actions{display:flex;align-items:center;gap:8px;flex:0 0 auto}

/* Update banner: present but not alarming -- an available version is news, not a problem. */
.upd-bar{display:flex;align-items:center;gap:14px;padding:13px 16px;margin-bottom:16px;
  border-radius:12px;background:color-mix(in srgb,var(--accent) 7%,transparent);border:1px solid color-mix(in srgb,var(--accent) 26%,transparent)}
.upd-ico{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;
  background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--primary);flex:0 0 auto}
.upd-ico svg{width:17px;height:17px}
.upd-txt{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:2px}
.upd-txt b{font-size:13.5px;color:var(--text)}
.upd-txt span{font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.upd-act{display:flex;gap:8px;flex:0 0 auto}
@media (max-width:640px){ .upd-bar{flex-wrap:wrap} .upd-act{width:100%;justify-content:flex-end} }

/* Failed balancer updates: amber, not red -- they are still serving viewers, just on the old
   release. Red would say "outage" and send the operator looking for one. */
.upd-bar.warn{background:rgba(251,191,36,.07);border-color:rgba(251,191,36,.28)}
.upd-bar.warn .upd-ico{background:rgba(251,191,36,.14);color:var(--warn)}
.lbfail-list{margin:6px 0 0;padding-left:16px;font-size:11.5px;color:var(--muted);line-height:1.6}
.lbfail-list code{font-family:var(--mono);font-size:11px;color:var(--text)}

/* A server running an older release than its panel. Amber, not red: it is still serving --
   the risk is that it and the main disagree about the schema, not that anything is down. */
.ver-tag{display:inline-block;margin-left:6px;padding:1px 7px;border-radius:20px;font-size:10px;
  font-weight:600;letter-spacing:.02em;vertical-align:1px;white-space:nowrap}
.ver-tag.old{color:var(--warn);background:rgba(251,191,36,.13);border:1px solid rgba(251,191,36,.3)}

/* Theme picker in the account menu. Swatches show the actual palette rather than an icon, so the
   choice is visible before it is made. */
/* Appearance picker in the topbar. Same menu language as the account menu so the two read
   as one system, but anchored to its own button. */
.thm-wrap{position:relative}
.thm-btn{width:34px;height:34px;border-radius:10px;border:1px solid var(--line);
  background:var(--input);color:var(--muted);cursor:pointer;display:inline-flex;
  align-items:center;justify-content:center;transition:.14s;padding:0}
.thm-btn svg{width:17px;height:17px}
.thm-btn:hover{color:var(--accent);border-color:color-mix(in srgb,var(--accent) 55%,transparent);
  background:color-mix(in srgb,var(--accent) 12%,transparent)}
.thm-menu{display:none;position:absolute;right:0;top:calc(100% + 8px);z-index:120;min-width:172px;
  padding:5px;border-radius:12px;border:1px solid var(--menu-line);
  background:linear-gradient(180deg,var(--menu-1),var(--menu-2));
  box-shadow:0 18px 40px -12px rgba(0,0,0,.6)}
.thm-menu.open{display:block}
.thm-menu button{display:flex;align-items:center;gap:10px;width:100%;padding:8px 10px;
  border:0;border-radius:8px;background:none;color:var(--muted);font:inherit;font-size:13px;
  cursor:pointer;text-align:left}
.thm-menu button svg{width:15px;height:15px;flex:none}
.thm-menu button:hover{background:var(--raise-1);color:var(--text)}
.thm-menu button.on{background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--text);
  font-weight:600}
@media (max-width:520px){ .thm-btn,.acct-btn{width:30px;height:30px} }

.acct-theme{padding:9px 10px 4px}
.acct-theme-lbl{display:block;font-size:10px;font-weight:600;letter-spacing:.05em;
  text-transform:uppercase;color:var(--faint);margin-bottom:7px}
.theme-pick{display:flex;gap:5px}
.theme-pick button{flex:1;display:flex;flex-direction:column;align-items:center;gap:5px;
  padding:7px 4px;border:1px solid var(--line);border-radius:9px;background:transparent;
  color:var(--muted);font-size:10.5px;cursor:pointer;transition:all .13s}
.theme-pick button:hover{border-color:var(--line-strong);color:var(--text)}
.theme-pick button.on{border-color:var(--primary);color:var(--text);
  background:color-mix(in srgb,var(--primary) 12%,transparent)}
.tsw{width:100%;height:16px;border-radius:5px;border:1px solid var(--line)}
.tsw-dark{background:linear-gradient(135deg,#0b0f17 50%,#223047 50%)}
.tsw-black{background:linear-gradient(135deg,#000 50%,#2a2a2a 50%)}
.tsw-light{background:linear-gradient(135deg,#f5f1e8 50%,#ddd5c4 50%)}
.tsw-light2{background:linear-gradient(135deg,#ffffff 50%,#1d6fd0 50%)}

/* Loading placeholders for a table whose filter just changed. The same row height as real
   content -- measured off a row still on screen -- and the same cells with the same classes,
   so nothing moves when the real rows land. The point of a placeholder is that the eye stays
   where it was; one that is a different shape defeats it. */
.sk-row td{padding:12px 16px;border-bottom:1px solid var(--line-soft);vertical-align:middle}
.sk-bar{display:block;height:12px;border-radius:6px;
  background:linear-gradient(90deg,var(--raise-1) 25%,var(--raise-2) 37%,var(--raise-1) 63%);
  background-size:400% 100%;animation:skPulse 1.2s ease-in-out infinite}
/* The name cell's two lines. A real one carries the channel -- id, name, category chip, and
   sometimes an NVIDIA badge -- and under it the servers it runs on. It is the widest thing in
   the row by a distance, so the bars standing for it are the largest here; sized against that
   line rather than against the narrow figures in the columns to its right. */
.sk-name{height:15px;width:64%}
.sk-sub{height:11px;width:42%;margin-top:7px;opacity:.7}
/* Centred, to sit under the headings whose columns are centred. */
.sk-mid{margin-left:auto;margin-right:auto}
/* The checkbox, and the four round action buttons -- shapes, not bars, because that is what
   is there.

   NOT .sk-box / .sk-btn: the sk- prefix already belongs to the confirm dialog in layout.php,
   whose .sk-box is a 420px panel with 26px of padding. This rule only overrode its width and
   height, so the placeholder's checkbox inherited that padding and came out four times the
   size of the checkbox it stands for. */
.skc-chk{display:block;width:15px;height:15px;border-radius:4px;
  background:linear-gradient(90deg,var(--raise-1) 25%,var(--raise-2) 37%,var(--raise-1) 63%);
  background-size:400% 100%;animation:skPulse 1.2s ease-in-out infinite}
.skc-btn{display:inline-block;width:26px;height:26px;border-radius:8px;margin-left:6px;
  background:linear-gradient(90deg,var(--raise-1) 25%,var(--raise-2) 37%,var(--raise-1) 63%);
  background-size:400% 100%;animation:skPulse 1.2s ease-in-out infinite}
.skc-btn:first-child{margin-left:0}
@keyframes skPulse{0%{background-position:100% 0}100%{background-position:0 0}}
@media (prefers-reduced-motion:reduce){.sk-bar,.skc-chk,.skc-btn{animation:none}}

/* Dismiss control inside the rebuild badge. Deliberately small and quiet: the badge is the
   message, and the x is only there so a badge that has been read can be silenced. */
.ver-x{background:none;border:0;padding:0 0 0 5px;margin:0;color:inherit;opacity:.55;
  cursor:pointer;font-size:13px;line-height:1}
.ver-x:hover{opacity:1}

/* User-agent tooltip. Wide enough for a full UA string on one or two lines, and it appears at
   once -- the browser's own title tooltip waits a second and truncates, which defeats the point. */
#uaTip{position:fixed;z-index:400;max-width:min(560px,92vw);padding:8px 11px;border-radius:9px;
  background:var(--surface);border:1px solid var(--line);color:var(--text);
  font-family:var(--mono);font-size:11px;line-height:1.5;word-break:break-all;
  box-shadow:var(--shadow-hard);display:none;pointer-events:none}
#uaTip.open{display:block}
.log-ua[data-ua]{cursor:pointer;border-bottom:1px dotted var(--line-strong)}
.log-ua[data-ua]:hover{color:var(--primary)}

/* Server-side filter bar on the activity page. Same toolbar shell as Manage Streams, one row. */
/* Secondary filter bar under a table toolbar.
 *
 * Scoped through .srv-table-card so specificity beats the base `input,select { width:100% }`
 * rule outright, rather than relying on appearing later in the file -- source order is a fragile
 * thing to depend on once a stylesheet has sections appended to it.
 *
 * Controls size to their content and do NOT stretch: an earlier `flex:1 1 120px` made three
 * filters on the users page inflate to fill the row while seven on the activity page looked
 * fine -- one rule, two very different results.
 */
.srv-table-card > .filter-bar{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  padding:10px 16px;border-bottom:1px solid var(--line);
  background:var(--panel)}

.srv-table-card > .filter-bar > select,
.srv-table-card > .filter-bar > input[type="text"]{
  flex:0 0 auto;
  width:auto;
  min-width:132px;
  max-width:190px;
  padding:7px 10px;
  font-size:12.5px;
  line-height:1.25;
  height:auto;
  background:var(--inset);
  border:1px solid var(--line);
  border-radius:8px;
  color:var(--text)}

/* The arrow is drawn by the base select rule; keep room for it at this smaller padding. */
.srv-table-card > .filter-bar > select{padding-right:28px;background-position:right 10px center;
  cursor:pointer}
.srv-table-card > .filter-bar > select:focus,
.srv-table-card > .filter-bar > input:focus{border-color:var(--primary);box-shadow:none}
.srv-table-card > .filter-bar > input::placeholder{color:var(--faint)}

/* Free-text fields hold real strings, not short labels, so they get more room. */
.srv-table-card > .filter-bar > input#f_ua{min-width:180px;max-width:250px}

/* Clear sits at the far end of the row on both pages. */
.srv-table-card > .filter-bar > .btn{flex:0 0 auto;margin-left:auto}

/* Narrow screens: fixed widths would force a horizontal scroll, so let them share the row. */
@media (max-width:820px){
  .srv-table-card > .filter-bar > select,
  .srv-table-card > .filter-bar > input[type="text"]{flex:1 1 140px;max-width:none}
  .srv-table-card > .filter-bar > .btn{margin-left:0}
}

/* Sticky toolbars for long tables.
 *
 * Scrolling hundreds of rows and losing the controls that produced them means scrolling back up
 * to change anything -- the filters belong on screen as long as the rows they filter are.
 *
 * The offsets come from syncStickyOffsets() in the layout, which measures the real heights of the
 * bars above each header. They were hardcoded at first, and the guess was 12px short of the
 * toolbar's actual height, so the header row overlapped the bar it was supposed to sit under.
 * Content decides those heights -- font, padding, whether the row wrapped -- so measuring is the
 * only version that stays correct. The fallbacks below only matter for the instant before the
 * first measurement lands.
 */
.srv-table-card{--stick-bar:var(--topbar-h); --stick-flt:calc(var(--topbar-h) + 65px);
}

.srv-table-card > .stream-toolbar{position:sticky;top:var(--stick-bar);z-index:22;
  background:var(--panel)}
.srv-table-card > .filter-bar{position:sticky;top:var(--stick-flt);z-index:21;
  background:var(--panel)}
/* The header row is deliberately NOT sticky.
 *
 * .table-wrap carries `overflow-x:auto`, and CSS promotes overflow-y to `auto` whenever
 * overflow-x is not `visible` -- so the wrapper is a scrolling container. A sticky <th> inside it
 * is positioned against THAT container, not the viewport, which put the header 182px down from
 * the top of the table and floating over the middle of the data.
 *
 * Making it work would mean dropping the horizontal scroll these wide tables need. The toolbars
 * above stay sticky and cost nothing: they sit outside .table-wrap, so they measure against the
 * page as intended.
 */

/* ---- Card source tabs -------------------------------------------------------------------
   A row of pills naming what the meters below them describe. Pills, not an underlined tab
   bar: everything else on this card is a soft rounded box -- the stat tiles, the meter
   tracks, the PORT/IN/OUT cells -- and a bar with a hard 2px underline belongs to a
   different design entirely. It read as bolted on because it was.

   Every card carries the row, a server with no GPU included: one pill still labels the block
   below it, and the row has to take the same height on every card or the grid stops lining
   up -- which is the one thing this page cannot afford. The height it takes is the padding
   the meter block gives back (see .srv-tabs + .srv-bars), so nothing is squeezed. */
.srv-tabs{display:flex;align-items:center;gap:4px;padding:8px 20px 9px;background:var(--panel)}
.srv-tab{flex:0 1 auto;display:inline-flex;align-items:center;gap:5px;padding:3px 9px;
  border-radius:999px;background:transparent;border:1px solid transparent;color:var(--faint);
  font:inherit;font-size:9.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  cursor:pointer;white-space:nowrap;transition:background .14s ease,color .14s ease,
  border-color .14s ease}
.srv-tab svg{width:11px;height:11px;opacity:.7}
.srv-tab:hover{color:var(--muted);background:var(--raise-1)}
.srv-tab.is-on{color:var(--primary);background:color-mix(in srgb,var(--primary) 11%,transparent);
  border-color:color-mix(in srgb,var(--primary) 28%,transparent)}
.srv-tab.is-on svg{opacity:1}
/* One pill and nothing to switch to: a label, not a control. It should not offer a hover
   state for a click that does nothing. */
.srv-tab.is-only{cursor:default;padding-left:0;background:transparent;border-color:transparent;
  color:var(--faint)}
.srv-tab.is-only:hover{background:transparent;color:var(--faint)}
/* A card the box HAS but cannot be read keeps its pill, so the fault is visible without
   clicking into it -- hiding it would make a broken GPU server look like one that never had
   a card. */
.srv-tab.is-dead{color:var(--t-warn,#fbbf24)}
.srv-tab.is-dead.is-on{background:color-mix(in srgb,var(--t-warn,#fbbf24) 12%,transparent);
  border-color:color-mix(in srgb,var(--t-warn,#fbbf24) 34%,transparent)}
.srv-tab-warn{display:inline-grid;place-items:center;width:12px;height:12px;border-radius:50%;
  background:var(--t-warn,#fbbf24);color:#1b1400;font-size:9px;font-weight:800;line-height:1}

/* A temperature is not a percentage: the bar is scaled against a sensible ceiling so 52C
   does not read as "52% full", and it warms and reddens on the thresholds that matter for
   a card rather than the ones that matter for a disk. */
.meter-v.is-temp{font-variant-numeric:tabular-nums}

/* ---- NVIDIA badge ------------------------------------------------------------------------
   One mark for one fact, shared by every page that states it -- the dashboard's server card,
   Manage Servers, and the row of a transcoded channel. It lived in the dashboard's own style
   block, which meant a second page wanting it had to copy thirty lines that would then drift
   apart. */
/* Same badge as the one beside a transcoded stream in Manage Streams -- one mark for one
   fact, so an operator who learns it in one page already knows it in the other. */
.srv-nv-badge{ display:inline-flex; align-items:center; gap:5px; vertical-align:baseline;
  font-family:var(--sans); font-weight:800; font-size:9.5px; letter-spacing:.14em;
  text-transform:uppercase; white-space:nowrap; padding:2px 9px 2px 7px; border-radius:999px;
  /* NVIDIA's own green, not the panel's --live: this badge names a brand, and a brand that is
     nearly-but-not-quite its colour looks like a mistake rather than a nod. It sits on a dark
     plate in every theme, which is where that green was made to be read. */
  color:#76b900; background:linear-gradient(180deg,#1b2410,#0e1408);
  border:1px solid color-mix(in srgb,#76b900 42%,transparent);
  box-shadow:inset 0 1px 0 color-mix(in srgb,#76b900 20%,transparent),
             0 0 0 1px color-mix(in srgb,#000 30%,transparent),
             0 1px 6px -2px color-mix(in srgb,#76b900 55%,transparent);
  text-shadow:0 0 10px color-mix(in srgb,#76b900 45%,transparent); }
/* The eye mark, drawn rather than fetched: an <img> would be a network round trip per card. */
.srv-nv-badge::before{ content:''; width:9px; height:9px; flex:none; border-radius:50%;
  background:radial-gradient(circle at 50% 50%, #a6ff1f 0 32%, #76b900 33% 62%, transparent 63%);
  box-shadow:0 0 6px color-mix(in srgb,#76b900 70%,transparent); }
/* It still breathes, as asked -- now on the glow rather than the whole badge, so the wordmark
   never dims to the point of being hard to read. */
.srv-nv-badge{ animation:nvPulse 2.4s ease-in-out infinite; }
@keyframes nvPulse{
  0%,100%{ box-shadow:inset 0 1px 0 color-mix(in srgb,#76b900 20%,transparent),
                      0 0 0 1px color-mix(in srgb,#000 30%,transparent),
                      0 1px 6px -2px color-mix(in srgb,#76b900 55%,transparent); }
  50%    { box-shadow:inset 0 1px 0 color-mix(in srgb,#76b900 26%,transparent),
                      0 0 0 1px color-mix(in srgb,#000 30%,transparent),
                      0 0 12px 1px color-mix(in srgb,#76b900 42%,transparent); }
}
/* Anyone who asked the system to stop moving things gets a badge that simply sits there. */
@media (prefers-reduced-motion: reduce){ .srv-nv-badge{ animation:none; } }
/* A driver that is installed but not yet loaded is not a working card, and must not wear the
   brand's colour. Amber, same shape, no pulse -- it is a waiting state, not a live one. */
.srv-nv-badge.waiting{ color:var(--warn); animation:none;
  background:linear-gradient(180deg,color-mix(in srgb,var(--warn) 16%,#0e1408),#0e1408);
  border-color:color-mix(in srgb,var(--warn) 45%,transparent);
  box-shadow:inset 0 1px 0 color-mix(in srgb,var(--warn) 18%,transparent),
             0 0 0 1px color-mix(in srgb,#000 30%,transparent);
  text-shadow:none; }
.srv-nv-badge.waiting::before{
  background:radial-gradient(circle at 50% 50%, color-mix(in srgb,var(--warn) 80%,#fff) 0 32%,
             var(--warn) 33% 62%, transparent 63%);
  box-shadow:0 0 6px color-mix(in srgb,var(--warn) 60%,transparent); }

/* ---- The "+ Add" split button's dropdown ---------------------------------------------
   Lived in Manage Streams' own <style> until Manage Movies needed the same menu. Copying it
   would have made two definitions of one component, and this project has paid for that
   mistake enough times: it lives here now, and both pages read the same rules. */
.addstream-wrap{ position:relative; display:inline-block; }
.addstream-menu{ position:absolute; top:calc(100% + 6px); right:0; min-width:190px; z-index:60;
  background:linear-gradient(180deg,var(--menu-1),var(--menu-2)); border:1px solid var(--menu-line); border-radius:11px; padding:6px;
  box-shadow:0 18px 40px -12px rgba(0,0,0,.7), 0 6px 16px rgba(0,0,0,.5); opacity:0; pointer-events:none;
  transform:translateY(-6px) scale(.98); transition:opacity .14s, transform .14s; }
.addstream-menu.show{ opacity:1; pointer-events:auto; transform:translateY(0) scale(1); }
.addstream-item{ display:flex; align-items:center; gap:9px; width:100%; text-align:left; padding:9px 11px; border:none;
  background:none; color:var(--text,#e7ebf3); font:inherit; font-size:.92em; cursor:pointer; border-radius:7px; white-space:nowrap; }
.addstream-item svg{ width:16px; height:16px; color:var(--muted,#9aa3b2); flex:none; }
.addstream-item:hover{ background:color-mix(in srgb,var(--primary) 13%,transparent); }
.addstream-item:hover svg{ color:var(--primary); }

/* ---- Grouped <select> options ---------------------------------------------------------
   A native option cannot carry a badge, so grouping is how a select says "these belong
   together". The heading is the label, and the browser's default rendering of it (bold
   italic, inherited colour) reads as disabled text on a dark panel -- hence the explicit
   colour and weight. Put here rather than on one page: every select that groups anything
   should look the same. */
optgroup{ color:var(--muted); font-style:normal; font-weight:600; font-size:.92em;
  background:var(--deep); }
optgroup option{ color:var(--text); font-weight:400; font-size:1em;
  background:var(--panel); padding-left:6px; }

/* ---- The batch concurrency dialog, and a checkbox inside a form panel ----------------
   Written for the movie form, then needed verbatim by Manage Movies' selection bar. One
   component, one definition -- copying it was how .addstream-* ended up in two files. */
/* ---- The batch concurrency dialog ---- */
/* A tick inside a form panel. .mvf-ctl stretches every input to the full column, which is
   right for a text box and absurd for a checkbox -- so this one is sized explicitly and
   its label sits beside it instead of being squeezed into what is left. */
.mvf-check{ display:flex; align-items:flex-start; gap:10px; padding:11px 12px; cursor:pointer;
  border:1px solid var(--line); border-radius:11px; background:var(--inset); }
.mvf-check input[type=checkbox]{ width:16px; height:16px; flex:0 0 16px; margin:2px 0 0; }
.mvf-check .tx{ display:flex; flex-direction:column; gap:3px; min-width:0; }
.mvf-check .tx b{ font-size:13px; color:var(--text); }
.mvf-check .tx span{ font-size:12px; color:var(--muted); line-height:1.45; }
.mvq-opt{ display:flex; align-items:flex-start; gap:10px; padding:11px 12px; cursor:pointer;
  border:1px solid var(--line); border-radius:11px; background:var(--inset); margin-bottom:8px; }
.mvq-opt input{ margin-top:2px; flex:none; }
.mvq-tx{ display:flex; flex-direction:column; gap:3px; }
.mvq-tx b{ font-size:13px; color:var(--text); }
.mvq-tx span{ font-size:12px; color:var(--muted); line-height:1.45; }
.mvq-sub{ display:flex; flex-wrap:wrap; gap:14px; padding:0 12px 14px 34px; margin-top:-4px;
  margin-bottom:8px; font-size:12.5px; color:var(--muted); }
.mvq-sub label{ display:flex; align-items:center; gap:7px; }


/* ---- VOD rows: the movie/series table components ------------------------------------
   Written for Manage Movies and now read by Manage Series too. A show's row shows the
   same poster, the same status badge, the same delivery pill and the same server line
   as a film's, because they ARE the same things -- and two copies of a badge is how a
   panel ends up with two shades of the same green. The mv- prefix is kept: renaming it
   would touch several hundred call sites to say the same thing. */
/* Poster thumbnail in the first column. Fixed box so a missing or oversized image can
     never change the row height -- a list of 10,000 movies that reflows as posters load is
     unusable while it loads. */
  .mv-poster{ width:34px; height:48px; border-radius:5px; object-fit:cover; flex:0 0 auto;
    background:var(--inset); border:1px solid var(--line-soft); }
  .mv-poster-none{ display:inline-flex; align-items:center; justify-content:center;
    color:var(--faint); }
  .mv-poster-none svg{ width:16px; height:16px; }
  .mv-name-cell{ display:flex; align-items:center; gap:11px; min-width:0; }
  .mv-name-txt{ display:flex; flex-direction:column; gap:3px; min-width:0; }
  .mv-title{ display:flex; align-items:center; gap:5px; min-width:0; }
  /* The name, at the size and weight Manage Streams gives it -- 13px/600, the one thing on
     that page that is NOT the table's 12.5px (see `.ch-name-cell .ch-name-txt`). Both
     elements: a film's title is a <b>, a show's is an <a> to its episodes, and a link that
     inherits 400 next to a channel's bold name is exactly the difference you notice when the
     two tables sit side by side. */
  .mv-title b, .mv-title a{ font-weight:600; font-size:13px;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  /* Every other size is inherited on purpose. Manage Streams lets the source, the resolution
     and the server line take the table's own 12.5px, and the two pages are meant to read as
     one product -- so this page inherits it too. Every size that IS set below has a
     counterpart on that page set to the same number. */
  .mv-id{ font-weight:600; font-size:11.5px; color:var(--muted); white-space:nowrap;
    font-variant-numeric:tabular-nums; }
  .mv-cat{ font-weight:600; font-size:11px; color:var(--cat-text); white-space:nowrap;
    background:var(--cat-bg); border:1px solid color-mix(in srgb,var(--cat-text) 26%,transparent);
    padding:1px 8px; border-radius:999px; letter-spacing:.2px; }
  /* Short like the streams page: the full URL lives in the tooltip and the info dialog. */
  /* Source is a SHORT label like Manage Streams -- the URL is truncated in JS to a handful
     of characters, so the column stays narrow and the full value lives in the tooltip. */
  .mv-src{ font-family:var(--mono); color:var(--muted); white-space:nowrap; }
  .mv-res{ display:inline-flex; align-items:center; gap:6px; justify-content:center; }
  .mv-res .mono{ }
  .mv-res-cod{ font-size:10.5px; font-weight:600; letter-spacing:.4px; text-transform:uppercase;
    color:var(--muted); border:1px solid var(--line); border-radius:5px; padding:0 5px; }
  .mv-cont{ font-family:var(--mono); font-size:10.5px; text-transform:uppercase;
    letter-spacing:.4px; color:var(--muted); border:1px solid var(--line);
    border-radius:6px; padding:1px 7px; }
  .mv-empty{ padding:48px 20px; text-align:center; color:var(--muted); }
  .mv-empty svg{ width:34px; height:34px; opacity:.5; margin-bottom:10px; }
  .mv-count{ color:var(--muted); font-size:12px; white-space:nowrap; }
  .mv-more{ padding:14px; text-align:center; color:var(--muted); font-size:12.5px; }
  .mv-st{ display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
    font-size:11px; font-weight:600; letter-spacing:.2px; padding:2px 9px;
    border-radius:999px; border:1px solid var(--line); color:var(--muted);
    background:color-mix(in srgb,var(--text) 4%,transparent); }
  .mv-st.dim{ color:var(--faint); }
  .mv-st.ok{ color:var(--live); border-color:color-mix(in srgb,var(--live) 38%,transparent);
    background:color-mix(in srgb,var(--live) 13%,transparent); }
  .mv-st.err{ color:var(--down); border-color:color-mix(in srgb,var(--down) 38%,transparent);
    background:color-mix(in srgb,var(--down) 13%,transparent); }
  /* The Error badge is a <button>: it opens the log. Everything a button brings with it --
     its own font, its own metrics -- has to be undone so it stays the same pill as the rest. */
  button.mv-st{ font-family:inherit; line-height:1.5; cursor:pointer; }
  button.mv-st.err:hover{ background:color-mix(in srgb,var(--down) 22%,transparent);
    border-color:color-mix(in srgb,var(--down) 55%,transparent); }
  .mv-st.warn{ color:var(--warn); border-color:color-mix(in srgb,var(--warn) 30%,transparent);
    background:color-mix(in srgb,var(--warn) 10%,transparent); }
  .mv-st.enc{ color:var(--warn); border-color:color-mix(in srgb,var(--warn) 38%,transparent);
    background:color-mix(in srgb,var(--warn) 13%,transparent); }
  /* currentColor, not --warn: the spinner sits inside the badge and has to be whatever the
     badge is. Wired to amber it stayed amber inside a blue Moving badge, which reads as two
     states at once. */
  .mv-spin{ width:9px; height:9px; border-radius:50%; flex:none;
    border:2px solid color-mix(in srgb,currentColor 35%,transparent);
    border-top-color:currentColor; animation:mvspin .8s linear infinite; }
  @keyframes mvspin{ to{ transform:rotate(360deg); } }
  /* While the form page loads after an Add/Edit click, the wait cursor everywhere -- and it
     must beat the pointer rules above, hence !important on the whole document. */
  html.mv-waiting, html.mv-waiting *{ cursor:wait !important; }

  /* Serving server line under the id/name -- same shape as a channel's server line. The
     marker before the name is a live indicator of the movie's delivery/state:
       gray  = not started (Transcode/Copy waiting)   green = Online / Copy done / Symlink
       orange spinner = Encoding  blue spinner = Moving between servers
       red = failed    blue redirect glyph = Redirect source (origin) */
  .mv-srv{ display:inline-flex; align-items:center; gap:5px;
    color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  /* Both ends of a move, with the arrow between them dimmed so the two names read first. */
  .mv-srv-hop{ display:inline-flex; align-items:center; gap:5px; min-width:0; }
  .mv-srv-arrow{ width:13px; height:13px; flex:0 0 auto; color:var(--faint); }
  /* A route that has not happened yet. Dimmed rather than coloured: it is a plan, and it
     should not read with the same weight as the two states that are real -- a move under
     way, and a movie settled where it is served from. */
  .mv-srv-plan{ color:var(--faint); }
  /* The working leg of the chain. Movement instead of another label: the arrow between the
     two boxes that are actually talking right now says which stage is running, in the line
     that already names them. Colours are the status badge's own -- orange encoding, the
     same #4a9dff blue moving -- so the badge and the chain can never disagree.
     Only a running row animates; a planned route stays grey and still, and that contrast is
     what makes the motion mean anything. */
  .mv-srv-arrow.live.enc{ color:var(--warn); }
  .mv-srv-arrow.live.move{ color:#4a9dff; }
  .mv-srv-arrow.live{ animation:mvFlow 1.3s ease-in-out infinite; }
  @keyframes mvFlow{
    0%,100%{ transform:translateX(-2px); opacity:.4; }
    55%    { transform:translateX(2px);  opacity:1; }
  }
  /* Source and encoder are the same machine, so the chain is one name with no arrow and
     there is nothing to travel. The name pulses in place instead of nothing moving at all. */
  .mv-srv-work{ animation:mvWork 1.7s ease-in-out infinite; }
  @keyframes mvWork{ 0%,100%{ opacity:.5; } 55%{ opacity:1; } }
  /* The origin, when it is not one of our machines. Set apart from the server names beside
     it so the chain reads as "from out there, to here, to there". */
  .mv-srv-src{ font-style:italic; opacity:.85; }
  /* One colour table for BOTH markers: the resting dot fills with it, the working spinner
     strokes with it. Two tables would drift the moment one blue is edited -- which is why
     the colour is carried as `color` and each shape spends it its own way. */
  .mv-srv-mk.gray{   color:var(--faint); }
  .mv-srv-mk.green{  color:var(--live); }
  .mv-srv-mk.red{    color:var(--down); }
  .mv-srv-mk.orange{ color:var(--warn); }
  .mv-srv-mk.blue{   color:#4a9dff; }
  .mv-srv-dot{ width:7px; height:7px; border-radius:50%; flex:0 0 auto;
    background:currentColor; }
  /* Working (encoding or moving): a turning ring, not a pulse. A pulse only says "alive";
     a spinner says "a job is running", which is what these two states are -- and it is the
     movement the status badge already uses, so busy looks the same across the whole row.
     Geometry copied from .mv-spin on purpose: same size, same stroke, same 0.8s. */
  .mv-srv-spin{ width:9px; height:9px; border-radius:50%; flex:0 0 auto; box-sizing:border-box;
    border:2px solid color-mix(in srgb,currentColor 32%,transparent);
    border-top-color:currentColor; animation:mvspin .8s linear infinite; }
  /* The left-edge status strip, the same colour as the server dot -- like a channel row's
     strip. On the first cell as an inset shadow so it never affects the row's box. */
  .srv-table tbody tr.mv-strip td:first-child{ box-shadow:inset 3px 0 0 var(--faint); }
  .srv-table tbody tr.mv-strip-green  td:first-child{ box-shadow:inset 3px 0 0 var(--live); }
  .srv-table tbody tr.mv-strip-orange td:first-child{ box-shadow:inset 3px 0 0 var(--warn); }
  .srv-table tbody tr.mv-strip-red    td:first-child{ box-shadow:inset 3px 0 0 var(--down); }
  .srv-table tbody tr.mv-strip-blue   td:first-child{ box-shadow:inset 3px 0 0 #4a9dff; }
  .mv-srv-redir{ width:12px; height:12px; flex:0 0 auto; color:#4a9dff; }
  /* One rule for both: Redirect and Moving are different states but the same blue, and the
     row's .mv-srv-mk.blue is that blue too. Written once so they stay one colour. */
  .mv-st.info, .mv-st.move{ color:#4a9dff; border-color:color-mix(in srgb,#4a9dff 38%,transparent);
    background:color-mix(in srgb,#4a9dff 13%,transparent); }

  /* ---- The delivery badge, under the source ----
     Quiet by default: an outline and a small capital, because it appears on EVERY row and
     a row of shouting badges is a table nobody can read. The engine rides inside the same
     pill as a second, darker segment rather than as a separate tag -- "Transcode" and "GPU"
     are one fact, and splitting them into two pills reads as two. */
  /* One width for every row: the pill fills the column, so the labels line up down the page
     and only the address inside them varies. Ragged pills of six different widths were the
     thing that made this column unreadable. */
  .mv-dlv{ display:flex; width:100%; align-items:stretch; overflow:hidden;
    font:600 9.5px/1 var(--sans); letter-spacing:.06em; text-transform:uppercase;
    border-radius:5px; border:1px solid var(--line);
    color:var(--muted); background:var(--inset); }
  .mv-dlv i{ font-style:normal; padding:4px 7px; display:flex; align-items:center;
    flex:0 0 auto; }
  .mv-dlv b{ font-weight:700; padding:4px 7px; letter-spacing:.05em;
    display:flex; align-items:center; flex:0 0 auto;
    background:color-mix(in srgb,currentColor 16%,transparent);
    border-left:1px solid color-mix(in srgb,currentColor 24%,transparent); }
  /* The live address, last and pale. Its own type -- monospace, mixed case, no tracking --
     because it is an ADDRESS while the segments before it are labels; making them look
     alike would invite reading the path as another tag. Bounded so one long exchange URL
     cannot stretch the column past every other row. */
  /* Takes whatever the labels leave. min-width:0 is what lets it shrink at all -- without
     it a flex item refuses to go below its content and the long URLs would push the pill
     past the column instead of truncating inside it. */
  .mv-dlv u{ text-decoration:none; display:flex; align-items:center; padding:4px 8px;
    flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    font:400 10.5px/1 var(--mono); letter-spacing:0; text-transform:none;
    color:var(--muted); background:var(--surface);
    border-left:1px solid color-mix(in srgb,currentColor 24%,transparent); }
  /* One colour per delivery, taken from what the row already uses for the same idea:
     the blue of a redirect, the green of a file that is simply there, the amber of work. */
  .mv-dlv.proxy{ color:#4a9dff; border-color:color-mix(in srgb,#4a9dff 30%,transparent);
    background:color-mix(in srgb,#4a9dff 9%,transparent); }
  .mv-dlv.file{ color:var(--live); border-color:color-mix(in srgb,var(--live) 30%,transparent);
    background:color-mix(in srgb,var(--live) 9%,transparent); }
  .mv-dlv.tc{ color:var(--warn); border-color:color-mix(in srgb,var(--warn) 30%,transparent);
    background:color-mix(in srgb,var(--warn) 9%,transparent); }
  /* A Copy is not work: it says so by staying grey where the others are coloured. */
  .mv-dlv.tc.copy{ color:var(--muted); border-color:var(--line); background:var(--inset); }
  .mv-dlv.tc.gpu{ color:var(--violet); border-color:color-mix(in srgb,var(--violet) 30%,transparent);
    background:color-mix(in srgb,var(--violet) 9%,transparent); }

/* ============================================================================
   VOD form chrome -- shared by movie-form.php and series-form.php
   ----------------------------------------------------------------------------
   These lived inside movie-form.php's <style> until the series form needed the
   same hero, the same panels and the same TMDB box. A second copy of 230 lines
   is a second thing to edit, and the copy that is not edited is the bug: the two
   forms would have drifted apart the first time either was touched. The pickers
   stay in movie-form.php -- they belong to that page.
   ========================================================================= */
/* "No GPU server" notice under Encode on. */
.mvf-warn{ display:flex; align-items:flex-start; gap:8px; margin-top:8px; padding:8px 10px;
  border-radius:9px; font-size:12px; color:var(--warn);
  border:1px solid color-mix(in srgb,var(--warn) 38%,transparent);
  background:color-mix(in srgb,var(--warn) 12%,transparent); }
.mvf-warn svg{ width:15px; height:15px; flex:0 0 15px; margin-top:1px; }
/* ---- Form rows: label on the left half, control on the right ----
   A settings-sheet layout rather than stacked fields. The eye scans one column of names
   and one column of values, and a long form stops looking like a wall. Below 820px the
   two halves stack -- side by side on a phone leaves neither enough room to be usable. */
.mvf{ padding:4px 0; }
/* One horizontal inset for the whole sheet, so the rows, the TMDB box and the error
   banner all start and end on the same line. Set once as a variable rather than repeated
   on each block, which is how they drift apart. */
/* 5% of the sheet's own width, so the inset grows with the screen instead of staying a
   fixed gap that looks tight on a wide monitor and wasteful on a small one. Floored at
   16px: on a narrow phone 5% is a few pixels, which is no margin at all. */
.mvf{ --mvf-pad:max(16px, 5%); }
.mvf-row{ display:grid; grid-template-columns:1fr 1fr; gap:22px; align-items:center;
  padding:17px var(--mvf-pad); border-bottom:1px solid var(--line-soft); }
.mvf-row:last-child{ border-bottom:0; }
/* The label is a column (name over hint) but the icon belongs to the NAME, so it is
   floated out of that flow with a grid: icon in its own narrow column spanning both rows,
   text in the second. A flex row would put the icon beside the whole block and centre it
   against the hint, which reads as if it labelled the hint instead. */
.mvf-lab{ font-size:13.5px; font-weight:600; color:var(--text);
  display:grid; grid-template-columns:16px 1fr; column-gap:9px; row-gap:3px;
  align-items:start; }
.mvf-lab > svg{ width:15px; height:15px; opacity:.55; grid-row:1; grid-column:1;
  margin-top:1px; flex:none; }
.mvf-lab > svg + *{ grid-column:2; }
/* Everything after the icon sits in the text column, including the bare text node the
   browser wraps -- which it does not, so the hint is placed explicitly. */
.mvf-hint{ grid-column:2; font-weight:400; font-size:11.5px; color:var(--muted); }
.mvf-lab .req{ color:var(--t-down); font-weight:700; }
/* A row the pointer is over lifts its icon out of grey: a small, cheap signal that the
   whole row is one thing, label and field together. */
.mvf-row:hover .mvf-lab > svg{ opacity:.9; color:var(--accent); }
.mvf-ctl{ min-width:0; }
.mvf-ctl input, .mvf-ctl select, .mvf-ctl textarea{ width:100%; }
/* ---- Delivery options ---- */
.dl-opts{ display:flex; flex-direction:column; gap:8px; }
.dl-opt{ display:grid; grid-template-columns:16px 1fr; column-gap:10px; row-gap:2px;
  align-items:start; padding:10px 12px; border:1px solid var(--line); border-radius:10px;
  background:var(--inset); cursor:pointer; transition:border-color .13s, background .13s; }
.dl-opt input{ grid-row:1; grid-column:1; width:15px; height:15px; margin:1px 0 0;
  accent-color:var(--accent); cursor:pointer; }
.dl-opt .t{ grid-column:2; font-size:13px; font-weight:600; color:var(--text); }
.dl-opt .d{ grid-column:2; font-size:11.5px; color:var(--muted); line-height:1.5; }
.dl-opt .why{ grid-column:2; font-size:11px; color:var(--t-warn); display:none; }
.dl-opt:hover:not(.is-off){ border-color:color-mix(in srgb,var(--accent) 45%,transparent); }
.dl-opt.is-on{ border-color:var(--accent);
  background:color-mix(in srgb,var(--accent) 9%,transparent); }
/* Unavailable, not hidden. A vanishing option leaves the operator wondering whether it ever
   existed; a dimmed one that states its condition teaches them what to change. */
.dl-opt.is-off{ opacity:.5; cursor:not-allowed; }
.dl-opt.is-off .why{ display:block; }
.dl-opt.is-off input{ cursor:not-allowed; }

/* Section header: says what the card below is for, so the two halves are told apart at a
   glance rather than by reading the fields. */
.mvf-head{ padding:16px var(--mvf-pad) 14px; border-bottom:1px solid var(--line); }
.mvf-head h3{ font-size:14px; font-weight:700; letter-spacing:.01em; }
.mvf-head p{ margin:5px 0 0; font-size:12px; color:var(--muted); line-height:1.5; }
.mvf-full{ padding:18px var(--mvf-pad) 4px; }
.mvf-foot{ display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  padding:12px var(--mvf-pad); border-top:1px solid var(--line); background:var(--inset); }
.mvf-foot-t{ margin-right:auto; font-size:11.5px; color:var(--muted); }
@media (max-width:820px){
  .mvf-row{ grid-template-columns:1fr; gap:8px; align-items:start; }
}


/* ---- The batch header (bulk mode) ----
   Sized and spaced like the hero it replaces, so switching modes does not move the panels
   below. Deliberately plain: a batch has no poster to be dramatic with, and forty titles
   in a scroll box is the honest picture of what is about to be written. */
.mvb{ border:1px solid var(--line); border-radius:14px; background:var(--panel);
  padding:14px 16px; margin-bottom:14px; }
.mvb-top{ display:flex; align-items:center; gap:12px; }
.mvb-ttl{ display:flex; align-items:center; gap:11px; margin-right:auto; min-width:0; }
.mvb-n{ font:600 19px/1 var(--sans); color:var(--ink,#0b1020); background:var(--primary);
  border-radius:9px; padding:9px 12px; flex:none; }
.mvb-tx{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.mvb-tx b{ font-size:14px; color:var(--text); }
.mvb-tx span{ font-size:12px; color:var(--muted); overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; }
.mvb-list{ display:flex; flex-wrap:wrap; gap:6px; margin-top:12px; max-height:132px;
  overflow-y:auto; }
.mvb-chip{ font-size:11.5px; color:var(--muted); background:var(--inset);
  border:1px solid var(--line-soft); border-radius:7px; padding:4px 8px;
  max-width:260px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* The concurrency dialog and the panel checkbox now live in the shared stylesheet:
   Manage Movies opens the same dialog, and one component needs one definition. */
.mvb-prog{ margin-top:11px; padding:8px 11px; border-radius:9px; background:var(--inset);
  border:1px solid var(--line-soft); font-size:12px; color:var(--text);
  font-family:var(--mono); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.mvb-chip i{ font-style:normal; color:var(--faint); margin-left:6px;
  padding-left:6px; border-left:1px solid var(--line); }

/* ---- The movie hero ----------------------------------------------------------------
   Its own poster, blurred, behind it. Cinema does this because it works: the page takes
   the colour of the film on it, so adding a horror film and adding a cartoon do not look
   like the same screen. Nothing here is decoration for its own sake -- the backdrop IS the
   poster field, the title IS the name field, and the line under it is the metadata below
   read back as a viewer would see it. */
.mvh{ position:relative; border:1px solid var(--line); border-radius:14px; overflow:hidden;
  background:var(--panel); margin-bottom:16px; }
.mvh-bg{ position:absolute; inset:0; background-size:cover; background-position:center;
  filter:blur(26px) saturate(1.3); transform:scale(1.2); opacity:.34; }
/* A wash over the blur so text stays readable whatever the poster's colours are, and so a
   bright poster does not turn the panel into a lightbox. */
.mvh::after{ content:''; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(180deg,
    color-mix(in srgb,var(--panel) 55%,transparent),
    color-mix(in srgb,var(--panel) 88%,transparent)); }
.mvh-in{ position:relative; z-index:1; display:flex; align-items:flex-end; gap:20px;
  padding:22px; flex-wrap:wrap; }
/* 2:3, the poster ratio every provider uses, so the frame is the right shape before an
   image arrives and does not resize when one does. */
.mvh-poster{ flex:0 0 auto; width:118px; aspect-ratio:2/3; border-radius:10px;
  overflow:hidden; background:var(--deep); border:1px solid var(--line-strong);
  box-shadow:0 12px 28px -10px rgba(0,0,0,.6); display:grid; place-items:center;
  cursor:pointer; }
.mvh-poster img{ width:100%; height:100%; object-fit:cover; display:block; }
.mvh-poster-empty{ display:flex; flex-direction:column; align-items:center; gap:6px;
  color:var(--faint); font-size:10.5px; text-align:center; padding:6px; }
.mvh-poster-empty svg{ width:26px; height:26px; opacity:.55; }
.mvh-txt{ flex:1 1 260px; min-width:0; display:flex; flex-direction:column; gap:7px; }
.mvh-kick{ font-size:10px; font-weight:700; letter-spacing:.16em; text-transform:uppercase;
  color:var(--accent); }
/* The title is the field. A box around it would say "form"; without one it says "this is
   the film's name, and you can type over it". */
.mvh-title{ width:100%; background:transparent; border:0; padding:0; color:var(--text);
  font-size:30px; font-weight:800; letter-spacing:-.02em; line-height:1.15; }
.mvh-title::placeholder{ color:var(--faint); font-weight:700; }
.mvh-title:focus{ outline:0; box-shadow:0 2px 0 var(--accent); }
.mvh-meta{ font-size:12.5px; color:var(--muted); min-height:1em; }
.mvh-meta .dot{ opacity:.4; margin:0 7px; }
.mvh-meta .star{ color:var(--t-warn); font-weight:700; }
.mvh-chips{ display:flex; flex-wrap:wrap; gap:6px; margin-top:2px; }
.mvh-chip{ display:inline-flex; align-items:center; gap:5px; padding:3px 9px;
  border-radius:999px; font-size:10.5px; font-weight:700; letter-spacing:.04em;
  text-transform:uppercase; white-space:nowrap;
  color:var(--muted); border:1px solid var(--line); background:var(--inset); }
.mvh-chip.on{ color:var(--accent); border-color:color-mix(in srgb,var(--accent) 40%,transparent);
  background:color-mix(in srgb,var(--accent) 12%,transparent); }
.mvh-chip svg{ width:12px; height:12px; }
@media (max-width:560px){
  .mvh-in{ padding:16px; gap:14px; }
  .mvh-poster{ width:88px; }
  .mvh-title{ font-size:22px; }
}

/* ---- The two panels ------------------------------------------------------------------
   auto-fit with a minimum rather than a breakpoint: the layout answers "does a panel still
   fit" instead of "is the window narrower than a number someone picked once", so it is
   right on the screen sizes nobody tested. */
.mvf-panels{ display:grid; grid-template-columns:repeat(auto-fit,minmax(420px,1fr));
  gap:16px; align-items:start; }
.mvf-panels > .mvf{ margin-top:0 !important; }

/* Two short values on one line. Each half keeps the label-over-control shape it has when
   it is alone, so nothing about the field changes -- only how many share a row. */
.mvf-pair{ grid-template-columns:1fr 1fr; align-items:start; }
.mvf-half{ display:grid; grid-template-columns:1fr; gap:7px; min-width:0; }
.mvf-half .mvf-ctl{ min-width:0; }
.mv-srcsrv{ display:flex; align-items:center; gap:8px; margin-top:8px; }
.mv-srcsrv select{ flex:1 1 auto; min-width:0; }
/* Inside a panel that is now half the page, label-beside-control has no room for either.
   One column, label over field -- the same shape the pairs already use. */
.mvf-panels .mvf-row{ grid-template-columns:1fr; gap:8px; align-items:start; }
@media (max-width:640px){
  .mvf-pair{ grid-template-columns:1fr; gap:14px; }
}

/* form layout + TMDB + source pickers (the list page keeps the table styles) */
.mv-form-grid{ display:grid; grid-template-columns:1fr 1fr; gap:14px; }
@media (max-width:640px){ .mv-form-grid{ grid-template-columns:1fr; } }

/* ---- TMDB lookup ---- */
/* The first thing on the page, and the first thing to do: search TMDB and everything
   below fills itself. It used to sit inside the second panel, under a scroll, which made
   the fast path the hidden one -- an operator typing a plot by hand had simply never seen
   it. Sized and coloured as the primary action rather than as a helper box. */
.tm-box{ border:1px solid color-mix(in srgb,var(--accent) 42%,transparent);
  background:linear-gradient(180deg,
    color-mix(in srgb,var(--accent) 13%,transparent),
    color-mix(in srgb,var(--accent) 5%,transparent));
  border-radius:14px; padding:16px 18px; margin-bottom:16px;
  box-shadow:0 10px 26px -18px color-mix(in srgb,var(--accent) 70%,transparent); }
.tm-head{ display:flex; align-items:center; gap:9px; margin-bottom:12px; font-size:14px; }
.tm-head b{ font-size:14px; font-weight:700; letter-spacing:-.01em; }
.tm-head svg{ width:17px; height:17px; color:var(--accent); flex:none; }
.tm-head .faint{ font-size:11.5px; }
/* A taller field than the ones in the panels: this is the one an operator should reach for
   first, and it should look reachable. */
.tm-box .tm-search input{ height:42px; font-size:14px; }
/* Wraps rather than overflows: below ~430px the id and the button drop onto their own
   line instead of squeezing the search box down to nothing. */
.tm-row{ display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.tm-search{ position:relative; flex:1 1 240px; min-width:0; }
.tm-box .tm-search input{ width:100%; }
/* `input.tm-id`, not `.tm-id`: the stylesheet sets width:100% on input[type=text], which
   is specificity (0,1,1) -- a bare class is (0,1,0) and LOSES. The id box then took the
   whole row, collapsed the search field to a square and pushed Fetch out of the card. */
.tm-box input.tm-id{ width:132px; flex:0 0 132px; min-width:0; }
.tm-box .tm-row .btn{ flex:0 0 auto; }
/* The dropdown is absolutely positioned so it overlays the fields below instead of
   pushing the whole form down every time a letter is typed. */
.tm-results{ display:none; position:absolute; left:0; right:0; top:calc(100% + 5px); z-index:90;
  max-height:290px; overflow:auto; border-radius:10px; border:1px solid var(--menu-line);
  background:linear-gradient(180deg,var(--menu-1),var(--menu-2)); padding:5px;
  box-shadow:0 18px 40px -12px rgba(0,0,0,.6); }
.tm-results.open{ display:block; }
.tm-item{ display:flex; align-items:center; gap:10px; padding:6px 8px; border-radius:8px;
  cursor:pointer; }
.tm-item:hover, .tm-item.on{ background:color-mix(in srgb,var(--accent) 14%,transparent); }
.tm-item img{ width:34px; height:50px; object-fit:cover; border-radius:4px; flex:none;
  background:var(--inset); }
.tm-item .noimg{ width:34px; height:50px; border-radius:4px; flex:none; background:var(--inset);
  border:1px solid var(--line-soft); }
.tm-item .tx{ min-width:0; display:flex; flex-direction:column; gap:2px; }
.tm-item .tt{ font-size:13px; font-weight:600; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; }
.tm-item .sub{ font-size:11.5px; color:var(--muted); font-family:var(--mono); }
.tm-none{ padding:14px 10px; text-align:center; color:var(--muted); font-size:12.5px; }
.tm-note{ margin-top:8px; font-size:11.5px; color:var(--muted); min-height:15px; }
.tm-note.err{ color:var(--t-down); }
.tm-note.ok{ color:var(--t-live); }

/* ============================================================================
   Bulk selection: the row checkbox, and the bar that floats up when anything is
   ticked.
   ----------------------------------------------------------------------------
   It was written on Manage Streams, copied verbatim into Manage Movies -- the
   comment there said so -- and then Manage Series grew the same bar and got the
   MARKUP without the styles: an always-visible block of stacked buttons in the
   corner, because .bulk-bar's own `position:fixed` and its hidden transform were
   in a file that page does not read. Three users is two copies too many.
   ========================================================================= */
.col-sel{ width:20px; text-align:center; }
.ch-check{ display:inline-flex; align-items:center; justify-content:center; cursor:pointer; position:relative; }
.ch-check input{ position:absolute; opacity:0; width:0; height:0; }
.ch-check-box{ width:17px; height:17px; border-radius:5px; border:1.5px solid var(--line);
  background:var(--input); display:inline-block; transition:border-color .12s, background .12s; position:relative; }
.ch-check input:checked + .ch-check-box{ background:var(--primary); border-color:var(--primary); }
.ch-check input:checked + .ch-check-box::after{ content:""; position:absolute; left:5px; top:2px; width:4px; height:8px;
  border:solid var(--inset); border-width:0 2px 2px 0; transform:rotate(45deg); }
.ch-check:hover .ch-check-box{ border-color:var(--primary); }
.srv-table tbody tr.is-selected{ background:color-mix(in srgb, var(--primary) 7%, transparent); }
.bulk-bar{ position:fixed; left:50%; bottom:26px; transform:translateX(-50%) translateY(140%);
  z-index:120; opacity:0; transition:transform .22s cubic-bezier(.2,.9,.3,1.2), opacity .18s; pointer-events:none; }
.bulk-bar.show{ transform:translateX(-50%) translateY(0); opacity:1; pointer-events:auto; }
.bulk-bar-inner{ display:flex; align-items:center; gap:16px; padding:10px 14px;
  background:linear-gradient(180deg,var(--menu-1),var(--menu-2)); border:1px solid var(--menu-line); border-radius:13px;
  box-shadow:0 18px 44px -10px rgba(0,0,0,.7), 0 6px 16px rgba(0,0,0,.5), 0 1px 0 rgba(255,255,255,.05) inset; }
.bulk-count{ font-size:.92em; color:var(--muted); white-space:nowrap; padding-left:4px; }
.bulk-count b{ color:var(--text); font-size:1.05em; }
.bulk-actions{ display:flex; gap:8px; }
.bulk-btn{ display:inline-flex; align-items:center; gap:6px; padding:7px 14px; border-radius:9px; font:inherit; font-size:.88em;
  font-weight:600; cursor:pointer; border:1px solid var(--line); background:var(--input); color:var(--text);
  transition:border-color .12s, background .12s, color .12s; }
.bulk-btn svg{ width:15px; height:15px; }
.bulk-restart{ color:var(--t-live); }
.bulk-stop{ color:var(--t-down); }
.bulk-edit{ color:var(--t-violet); }
.bulk-remove{ color:var(--muted); }
/* Disabled until these actions are wired up: dim, no pointer, no hover lift. */
.bulk-btn[disabled]{ opacity:.45; cursor:not-allowed; }
.bulk-clear{ background:none; border:none; color:var(--muted); cursor:pointer; font:inherit; font-size:.85em;
  padding:4px 8px; border-radius:7px; }
.bulk-clear:hover{ color:var(--text); }

/* ═══════════ The info dialog (.mvi-*) ═══════════
   Shared, because three lists open the same dialog: a movie, an episode and a show. It
   started page-local in Manage Movies; the second page to want it is the moment a copy
   would begin drifting from the original. */
/* Info dialog */
/* Four across, not two. Thirteen facts in two columns is a tall ladder the eye climbs;
   in four it is three short rows that can be taken in at a glance -- and every value here
   is a word or a number, never a sentence. */
.mvi-grid{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:12px 20px; }
@media (max-width:720px){ .mvi-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
/* Grouped rather than listed. "What the file is" and "what your servers do with it" are
   two different questions, and a heading between them saves reading the labels to work out
   which is which. */
.mvi-block{ margin-top:16px; padding-top:14px; border-top:1px solid var(--line-soft); }
.mvi-block > h5{ margin:0 0 11px; font-size:11px; letter-spacing:.4px;
  text-transform:uppercase; color:var(--muted); font-weight:700; }
.mvi-wide{ grid-column:1/-1; }
.mvi-wide b{ font-size:11.5px; word-break:break-all; white-space:normal; }
.mvi-it{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.mvi-it small{ color:var(--muted); font-size:11px; letter-spacing:.3px; text-transform:uppercase; }
.mvi-it b{ font-weight:600; font-size:13px; overflow:hidden; text-overflow:ellipsis; }
/* Room under the player. Facts pressed against the bottom of a video read as part of it,
   and the eye needs a beat between something moving and something to be read. */
.mvi-stage{ border-radius:10px; overflow:hidden; background:#000; margin-bottom:18px; }
.mvi-stage video{ width:100%; max-height:380px; display:block; background:#000; }
/* A 16:9 box that keeps its shape whatever the dialog's width is, so the trailer does not
   jump the layout as it loads. */
.mvi-stage iframe{ width:100%; aspect-ratio:16/9; max-height:380px; display:block; border:0; }

/* ---- The head: poster, title, the facts, the chips ---- */
.mvi-head{ display:flex; gap:16px; align-items:center; margin-bottom:4px; }
/* Round, as asked. It crops the poster to its middle square -- at 88px beside the title
   it is an emblem, not the thing you read the film's name off, and the name is right
   next to it in type. */
.mvi-art{ width:88px; height:88px; flex:0 0 auto; border-radius:50%; object-fit:cover;
  background:var(--raise-1); border:1px solid var(--line);
  box-shadow:0 1px 2px rgba(var(--sh),.22), 0 6px 16px -4px rgba(var(--sh),.42); }
.mvi-hx{ min-width:0; flex:1 1 auto; }
.mvi-hx h4{ margin:0 0 2px; font-size:17px; font-weight:800; color:var(--text);
  line-height:1.25; }
.mvi-orig{ font-size:12px; color:var(--muted); margin-bottom:8px; }
.mvi-facts{ display:flex; flex-wrap:wrap; align-items:center; gap:7px; margin-bottom:9px; }
.mvi-chip{ font-size:11.5px; font-weight:600; color:var(--muted); background:var(--inset);
  border:1px solid var(--line-soft); border-radius:999px; padding:2px 9px; white-space:nowrap; }
.mvi-chip.rate{ color:var(--warn); border-color:color-mix(in srgb,var(--warn) 35%,transparent);
  background:color-mix(in srgb,var(--warn) 12%,transparent); }
.mvi-chip.tmdb{ color:var(--t-info); }
/* The episode's coordinates lead the row of chips, so they are read first and not hunted
   for among the genres. */
.mvi-chip.ep{ font-family:var(--mono); color:var(--text);
  border-color:color-mix(in srgb,var(--accent) 40%,transparent);
  background:color-mix(in srgb,var(--accent) 12%,transparent); }
.mvi-people{ font-size:12px; color:var(--muted); line-height:1.6; }
.mvi-people b{ color:var(--text); font-weight:600; }
.mvi-people span{ color:var(--text); }

.mvi-tabs{ display:flex; gap:6px; margin:0 0 10px; }
.mvi-tab{ font:600 12px/1 var(--sans); color:var(--muted); background:var(--raise-1);
  border:1px solid var(--line); border-radius:8px; padding:8px 14px; cursor:pointer; }
.mvi-tab.on{ color:var(--text); border-color:color-mix(in srgb,var(--accent) 55%,transparent);
  background:color-mix(in srgb,var(--accent) 12%,transparent); }

.mvi-sec{ margin-top:14px; }
.mvi-sec h5{ margin:0 0 5px; font-size:11px; letter-spacing:.3px; text-transform:uppercase;
  color:var(--muted); font-weight:700; }
.mvi-sec p{ margin:0; font-size:13px; line-height:1.65; color:var(--text); }

/* ══════════════════════════════════════════════════════════════════════════════
   Live Chat — reseller support (operator inbox + Telegram setup)
   The panel had no conversation component, so this is the one place that needed
   new grammar rather than cloning Manage Streams. Everything else on both pages
   is .srv-table-card / .stream-toolbar / .btn, unchanged.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── shared bits ─────────────────────────────────────────────────────────────── */
.lc-body{padding:16px}
/* Explanatory prose uses --muted, like every other description in the panel (.st-d,
   .rs-permtext i, .rg-pt i). --text is near-white (#e7eef8 dark, #f2f2f2 black) and is meant for
   headings and emphasis; a paragraph of it is what made this page hard to read in the dark
   themes. The bold inside stays at --text so the words that matter still carry. */
.lc-lead{margin:0 0 10px;font-size:13px;line-height:1.7;color:var(--muted)}
.lc-lead b, .lc-ol b{color:var(--text)}
.lc-hint{margin:8px 0 0;font-size:12px;line-height:1.65;color:var(--muted)}
.lc-warn{margin:10px 0 0;padding:9px 12px;border-radius:9px;font-size:12px;line-height:1.65;
  color:var(--text);background:color-mix(in srgb,var(--warn) 10%,transparent);
  border:1px solid color-mix(in srgb,var(--warn) 34%,transparent)}
.lc-err{margin-top:10px;padding:9px 12px;border-radius:9px;font-size:12px;color:var(--text);
  background:color-mix(in srgb,var(--down) 12%,transparent);
  border:1px solid color-mix(in srgb,var(--down) 38%,transparent)}
.lc-body code{font-family:var(--mono);font-size:12px;padding:1px 5px;border-radius:5px;
  background:var(--inset);border:1px solid var(--line-soft)}
.lc-body a{color:var(--primary)}

/* ── setup page ──────────────────────────────────────────────────────────────── */
.lc-setup{display:flex;flex-direction:column;gap:14px}
.lc-step{transition:opacity .2s}
.lc-num{display:inline-grid;place-items:center;width:22px;height:22px;border-radius:50%;
  font-size:12px;font-weight:700;color:var(--ink);background:var(--primary)}
.lc-pill{font-size:11px;font-weight:600;padding:3px 9px;border-radius:999px;
  background:var(--inset);border:1px solid var(--line);color:var(--muted)}
.lc-pill.on{color:var(--live);border-color:color-mix(in srgb,var(--live) 45%,transparent);
  background:color-mix(in srgb,var(--live) 12%,transparent)}
.lc-pill.warn{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 45%,transparent);
  background:color-mix(in srgb,var(--warn) 12%,transparent)}
.lc-pill.off{color:var(--faint)}
.lc-ok{font-size:11px;font-weight:600;color:var(--live)}
.lc-ol{margin:0;padding-left:20px;font-size:13px;line-height:1.9;color:var(--muted)}
.lc-ol li{margin-bottom:2px}
.lc-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.lc-input{flex:1;min-width:280px;padding:9px 12px;background:var(--inset);
  border:1px solid var(--line);border-radius:9px;color:var(--text);
  font-family:var(--mono);font-size:12.5px}
.lc-code{font-family:var(--mono);font-size:19px;font-weight:700;letter-spacing:3px;
  padding:7px 14px;border-radius:9px;color:var(--text);background:var(--inset);
  border:1px dashed var(--line-strong)}
.lc-connected{display:flex;align-items:center;gap:12px;margin-top:12px;padding:10px 12px;
  border-radius:9px;font-size:13px;background:color-mix(in srgb,var(--live) 9%,transparent);
  border:1px solid color-mix(in srgb,var(--live) 32%,transparent)}
.lc-connected span{flex:1}
.lc-switch{display:inline-flex;align-items:center;gap:9px;cursor:pointer;font-size:13px}
.lc-switch input{position:absolute;opacity:0;width:0;height:0}
.lc-switch span{width:38px;height:21px;border-radius:999px;background:var(--inset);
  border:1px solid var(--line);position:relative;transition:background .15s,border-color .15s}
.lc-switch span::after{content:"";position:absolute;top:2px;left:2px;width:15px;height:15px;
  border-radius:50%;background:var(--muted);transition:transform .15s,background .15s}
.lc-switch input:checked + span{background:color-mix(in srgb,var(--live) 26%,transparent);
  border-color:color-mix(in srgb,var(--live) 55%,transparent)}
.lc-switch input:checked + span::after{transform:translateX(17px);background:var(--live)}
.lc-switch input:disabled + span{opacity:.45}
.lc-sample{padding:12px 14px;border-radius:10px;font-size:13px;line-height:1.7;
  background:var(--inset);border:1px solid var(--line-soft)}
.lc-foot{font-size:12px;color:var(--muted);padding:2px 4px}
.lc-badfoot{color:var(--down)}

/* ── inbox ───────────────────────────────────────────────────────────────────── */
.lc-inbox{display:grid;grid-template-columns:340px 1fr;gap:14px;
  height:calc(100vh - var(--topbar-h) - 48px);min-height:460px}
.lc-threads,.lc-conv{display:flex;flex-direction:column;min-height:0}
/* clip, not hidden: `overflow:hidden` would make the card a scroll container and the
   toolbar above would stick to the card instead of staying put. */
.lc-tlist{flex:1;overflow-y:auto;overflow-x:clip}
.lc-trow{display:flex;align-items:center;gap:10px;width:100%;padding:11px 14px;
  background:none;border:0;border-bottom:1px solid var(--line-soft);cursor:pointer;
  text-align:left;color:var(--text);transition:background .12s}
.lc-trow:hover{background:color-mix(in srgb,var(--accent) 6%,transparent)}
.lc-trow.on{background:color-mix(in srgb,var(--primary) 12%,transparent)}
/* Selection for the operator's delete. The tick sits before the avatar and only shows itself
   on hover, on the row being read, or once it is ticked -- a column of empty boxes down the
   side of an inbox turns a list you read into a form you fill in. */
.lc-trow .lc-tsel{flex:none;opacity:0;transition:opacity .12s}
.lc-trow:hover .lc-tsel,.lc-trow.on .lc-tsel,.lc-trow.sel .lc-tsel{opacity:1}
.lc-trow.sel{background:color-mix(in srgb,var(--down) 10%,transparent)}
.lc-trow.sel:hover{background:color-mix(in srgb,var(--down) 14%,transparent)}
.lc-all{flex:none;margin-left:8px}
.lc-av{display:grid;place-items:center;width:34px;height:34px;border-radius:50%;flex:none;
  font-size:13px;font-weight:700;color:var(--ink);background:var(--primary)}
.lc-tmid{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.lc-tname{font-size:13px;font-weight:600}
.lc-tprev{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lc-tright{display:flex;flex-direction:column;align-items:flex-end;gap:4px;flex:none}
.lc-tago{font-size:11px;color:var(--faint);font-variant-numeric:tabular-nums}
.lc-unread{min-width:18px;height:18px;padding:0 5px;border-radius:999px;display:grid;
  place-items:center;font-size:11px;font-weight:700;color:var(--ink);background:var(--primary)}
.lc-msgs{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px}
.lc-msg{display:flex;flex-direction:column;max-width:74%}
.lc-msg.them{align-self:flex-start}
.lc-msg.me{align-self:flex-end;align-items:flex-end}
.lc-bub{padding:9px 13px;border-radius:13px;font-size:13px;line-height:1.65;
  white-space:pre-wrap;word-break:break-word}
.lc-msg.them .lc-bub{background:var(--inset);border:1px solid var(--line-soft);
  border-bottom-left-radius:4px}
.lc-msg.me .lc-bub{background:color-mix(in srgb,var(--primary) 20%,var(--inset));
  border:1px solid color-mix(in srgb,var(--primary) 40%,transparent);border-bottom-right-radius:4px}
.lc-meta{display:flex;align-items:center;gap:7px;margin-top:3px;padding:0 3px;
  font-size:11px;color:var(--faint);font-variant-numeric:tabular-nums}
.lc-viatag{font-size:10px;padding:1px 6px;border-radius:999px;
  background:var(--inset);border:1px solid var(--line-soft);color:var(--muted)}
/* The compose box stays put while the reader scrolls back through the conversation.

   On a wide screen the card is already viewport-height and its message list scrolls inside,
   so this does nothing. Below 1100px .lc-inbox drops to height:auto, the card grows with the
   conversation and the PAGE is what scrolls -- and the box a customer wants to type in slides
   off the bottom, so they have to scroll all the way down again just to answer. Sticky pins it
   to the foot of the viewport for exactly as long as the card is on screen.

   It needs an opaque background of its own: messages pass underneath it now. */
.lc-compose{display:flex;align-items:flex-end;gap:10px;padding:12px 14px;
  border-top:1px solid var(--line);position:sticky;bottom:0;z-index:3;
  background:var(--panel)}
.lc-compose textarea{flex:1;resize:none;padding:10px 12px;background:var(--inset);
  border:1px solid var(--line);border-radius:10px;color:var(--text);
  font-family:var(--sans);font-size:13px;line-height:1.5;max-height:120px}
.lc-empty{padding:26px 18px;text-align:center;font-size:13px;color:var(--muted);line-height:1.8}

@media (max-width:1100px){
  .lc-inbox{grid-template-columns:1fr;height:auto}
  .lc-tlist{max-height:320px}
  .lc-msgs{min-height:360px}
}

/* Reseller side: one conversation, no thread list, so the card simply fills the page. */
.lc-rs{height:calc(100vh - var(--topbar-h) - 48px);min-height:460px;display:flex}
.lc-rs .lc-conv{flex:1;min-height:0}
.lc-stored{margin-top:10px;padding:8px 12px;border-radius:9px;font-size:12.5px;color:var(--muted);
  background:var(--inset);border:1px solid var(--line-soft)}
.lc-stored b{color:var(--text)}

/* Unread support messages, in either sidebar. Red because it is the one number in the panel
   that means "someone is waiting for you". */
.nav-badge{margin-left:auto;min-width:19px;height:19px;padding:0 6px;border-radius:999px;
  display:inline-grid;place-items:center;font-size:11px;font-weight:700;line-height:1;
  color:#fff;background:var(--down);font-variant-numeric:tabular-nums;
  box-shadow:0 0 0 1px color-mix(in srgb,var(--down) 45%,transparent)}
/* An INLINE badge steals width from the label: .sb-htxt is flex:1 with text-overflow:ellipsis,
   so a count beside it turned "Reseller Support" into "Reseller Sup…". The badge therefore floats
   over its own icon instead -- absolutely positioned, so it costs no layout width at all and the
   label can never be shortened by it. Both sidebars use .nav-item / .sb-hico, and both are
   already positioning contexts. */
.nav-badge.on-icon{position:absolute;margin:0;min-width:15px;height:15px;padding:0 4px;
  top:2px;left:20px;font-size:10px;border-radius:999px;
  /* A ring in the sidebar's own colour lifts the pill off the icon underneath it. */
  box-shadow:0 0 0 2px var(--panel-2,var(--panel))}
/* The section head's icon is a 30px tile and the badge lives inside it, so it anchors to that
   box rather than to the row. */
.sb-hico .nav-badge.on-icon{top:-5px;left:auto;right:-5px}

/* ── Tickets ─────────────────────────────────────────────────────────────────
   Built on the Live Chat grammar (.lc-inbox / .lc-conv / .lc-msg) so the two pages
   in Reseller Support read as one product. Only what a ticket has and a chat does
   not is new: a status, and images. */
/* The same pill Movies uses for an encode state (.mv-st): same metrics, same border, same
   tinted fill. One badge shape across the panel, so a status always looks like a status. */
/* A sixth tone for the shared status badge, added to .mv-st's own vocabulary rather than
   invented per page: Resolved needed a colour of its own once green went to Open and red to
   Closed, and faint read as "filed away" for a state that means "we fixed it". Violet is the
   panel's remaining accent and is already themed in all three skins. */
.mv-st.violet{color:var(--t-violet,var(--violet));
  border-color:color-mix(in srgb,var(--violet) 38%,transparent);
  background:color-mix(in srgb,var(--violet) 13%,transparent)}
.tk-pill{display:inline-flex;align-items:center;gap:6px;white-space:nowrap;font-size:11px;
  font-weight:600;letter-spacing:.2px;padding:2px 9px;border-radius:999px;
  border:1px solid var(--line);color:var(--muted);
  background:color-mix(in srgb,var(--text) 4%,transparent)}
/* Colour carries the meaning the operator sorts by: something is waiting on THEM (open,
   replied) versus waiting on the reseller (answered) versus finished. */
.tk-pill.open{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 42%,transparent);
  background:color-mix(in srgb,var(--warn) 12%,transparent)}
.tk-pill.replied{color:var(--down);border-color:color-mix(in srgb,var(--down) 42%,transparent);
  background:color-mix(in srgb,var(--down) 12%,transparent)}
.tk-pill.answered{color:var(--primary);border-color:color-mix(in srgb,var(--primary) 42%,transparent);
  background:color-mix(in srgb,var(--primary) 12%,transparent)}
.tk-pill.resolved{color:var(--live);border-color:color-mix(in srgb,var(--live) 42%,transparent);
  background:color-mix(in srgb,var(--live) 12%,transparent)}
.tk-pill.closed{color:var(--faint)}
.tk-acts{display:flex;gap:6px;margin-left:auto}
.tk-clipn{font-size:11px;color:var(--faint)}
.tk-compose{align-items:center}
.tk-clip{display:inline-flex;align-items:center;gap:4px;padding:8px 10px;border-radius:9px;
  cursor:pointer;color:var(--muted);background:var(--inset);border:1px solid var(--line);
  font-size:12px;font-variant-numeric:tabular-nums}
.tk-clip:hover{color:var(--text);border-color:var(--line-strong)}
.tk-clip svg{width:16px;height:16px}
.tk-imgs{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.tk-img{display:block;border-radius:8px;overflow:clip;border:1px solid var(--line-soft);line-height:0}
.tk-img img{max-width:190px;max-height:150px;object-fit:cover;display:block}
.tk-newbar{display:flex;gap:8px;padding:10px 14px;border-bottom:1px solid var(--line)}

/* Ticket table: the subject carries the row, so it gets the width and the truncation. */
/* The column owns the width now (table-layout:fixed), so the subject fills its cell and
   truncates against it instead of against a character count guessed here. */
.tk-subj{display:block;max-width:38ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-weight:600;color:var(--text)}
.tk-row-sub{font-size:11.5px;color:var(--faint)}
.tk-col-when{white-space:nowrap;font-variant-numeric:tabular-nums;color:var(--muted);font-size:12px}
/* The conversation, inside the View modal. */
.tk-conv{max-height:52vh;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:10px;
  background:var(--inset);border:1px solid var(--line-soft);border-radius:10px}
.tk-mfoot{display:flex;align-items:flex-end;gap:10px;margin-top:12px}
.tk-mfoot textarea{flex:1;resize:none;padding:10px 12px;background:var(--inset);
  border:1px solid var(--line);border-radius:10px;color:var(--text);font-family:var(--sans);
  font-size:13px;line-height:1.5;max-height:120px}
.tk-locked{padding:14px;text-align:center}

/* ── Ticket modals ───────────────────────────────────────────────────────────
   Two collisions, both from borrowing a layout without its overrides.

   1) .modal-head is a flex row with space-between and NO gap. Four children — subject,
      status pill, reseller name, close — met in the middle, and the subject had nothing
      telling it to shrink. It now takes the free space and truncates; the rest stay put.

   2) .mvf-row is a TWO-COLUMN grid (1fr 1fr), which is right on a wide form page and
      wrong inside a modal: the label and its field share a narrow width and read as
      overlapping. Channels and Cloudflare already override it inside their own modals
      for the same reason; these stack the label above the field. */
#tkView .modal-head, #tkNew .modal-head, #rtNew .modal-head{gap:12px}
#tkVSubj{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#tkVState, #tkVWho{flex:none}
#tkVWho{margin-left:0}
#tkNew .mvf-row, #rtNew .mvf-row{display:block}
#tkNew .mvf-lab, #rtNew .mvf-lab{display:block;margin-bottom:7px;font-weight:600}
#tkNew .mvf-row + .mvf-row, #rtNew .mvf-row + .mvf-row{margin-top:16px}
#tkNew .lc-input, #rtNew .lc-input{width:100%;box-sizing:border-box;font-family:var(--sans);
  font-size:13px}
#tkNew textarea.lc-input, #rtNew textarea.lc-input{resize:vertical;line-height:1.6}
#tkNew .lc-hint, #rtNew .lc-hint{display:inline;margin-left:6px;font-weight:400}
/* The conversation gets the height the wider modal can afford. */
#tkView .tk-conv{max-height:58vh}

/* The group chip Manage Resellers already draws, lifted out of that page so the ticket table
   can wear the same one instead of a copy that drifts. --gc is the group's own colour. */
.rs-chip{font-size:10.5px;font-weight:600;letter-spacing:.02em;padding:1px 8px;border-radius:999px;
  border:1px solid var(--line);color:var(--muted);background:var(--inset);white-space:nowrap}
.rs-chip[style*="--gc"]{color:var(--gc);
  border-color:color-mix(in srgb,var(--gc) 45%,transparent);
  background:color-mix(in srgb,var(--gc) 12%,transparent)}
.rs-chip.off{color:var(--down);border-color:color-mix(in srgb,var(--down) 40%,var(--line))}

/* ── Summary tiles, lifted out of the dashboard so every page can wear them ──────── */
/* Panel-wide summary cards — dark tiles with a subtle accent (icon in a tinted box). */
.totals{ display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-bottom:16px; }
.tcard{ display:flex; align-items:center; gap:13px; padding:14px 16px; border-radius:14px;
  background:var(--surface); border:1px solid var(--line); position:relative; overflow:hidden; min-width:0; }
.tcard::before{ content:''; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--acc); }
.tcard-ico{ width:42px; height:42px; border-radius:12px; display:flex; align-items:center; justify-content:center;
  background:var(--acc-bg); color:var(--acc); flex:0 0 auto; }
.tcard-ico svg{ width:21px; height:21px; }
.tcard-body{ min-width:0; flex:1; overflow:hidden; }
.tcard-v{ font-size:25px; font-weight:700; line-height:1.05; font-variant-numeric:tabular-nums; color:var(--text,#e6e9ef); white-space:nowrap; }
.tcard-l{ font-size:11px; color:var(--muted,#8a93a6); text-transform:uppercase; letter-spacing:.4px; margin-top:3px; font-weight:600; }
.tcard-dual{ display:flex; flex-direction:column; gap:7px; }
.tcard-drow{ display:flex; align-items:baseline; gap:10px; justify-content:space-between; min-width:0; }
.tcard-dv{ font-size:16px; font-weight:700; font-variant-numeric:tabular-nums; color:var(--text,#e6e9ef); white-space:nowrap; flex:0 0 auto; }
.tcard-dl{ font-size:9px; color:var(--muted,#8a93a6); text-transform:uppercase; letter-spacing:.3px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* The icon takes the theme's own hue, not a fixed pastel: on cream the bright dark-theme
   accents floated off the page, and on black they glared. --t-* are the same hues already
   tuned per theme for text, so the widgets match every badge in the panel. */
.tcard.t-blue{ --acc:var(--t-info); --acc-bg:color-mix(in srgb,var(--t-info) 13%,transparent); }
.tcard.t-green{ --acc:var(--t-live); --acc-bg:color-mix(in srgb,var(--t-live) 13%,transparent); }
.tcard.t-violet{ --acc:var(--t-violet); --acc-bg:color-mix(in srgb,var(--t-violet) 13%,transparent); }
.tcard.t-amber{ --acc:var(--t-warn); --acc-bg:color-mix(in srgb,var(--t-warn) 13%,transparent); }
@media(max-width:1100px){ .totals{ grid-template-columns:repeat(2,1fr); } }
@media(max-width:560px){ .totals{ grid-template-columns:1fr; } }

/* The glass variant: the same tile, given depth. A tinted pane over the surface, a lit top
   edge, a coloured glow beneath, and a highlight that sweeps across every few seconds. The
   sweep is decoration, so it is the first thing to go when the reader has asked for less
   motion. */
.tcard.glass{
  background:
    linear-gradient(135deg, color-mix(in srgb,var(--acc) 14%,transparent) 0%,
                            color-mix(in srgb,var(--acc) 3%,transparent) 46%,
                            transparent 100%),
    color-mix(in srgb,var(--surface) 78%,transparent);
  backdrop-filter:blur(14px) saturate(1.25);
  -webkit-backdrop-filter:blur(14px) saturate(1.25);
  border:1px solid color-mix(in srgb,var(--acc) 26%,var(--line));
  box-shadow:0 10px 26px -14px color-mix(in srgb,var(--acc) 55%,transparent),
             inset 0 1px 0 color-mix(in srgb,#ffffff 14%,transparent);
  transition:transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.tcard.glass:hover{
  transform:translateY(-2px);
  border-color:color-mix(in srgb,var(--acc) 42%,var(--line));
  box-shadow:0 16px 34px -16px color-mix(in srgb,var(--acc) 70%,transparent),
             inset 0 1px 0 color-mix(in srgb,#ffffff 20%,transparent);
}
.tcard.glass .tcard-ico{
  background:color-mix(in srgb,var(--acc) 18%,transparent);
  border:1px solid color-mix(in srgb,var(--acc) 30%,transparent);
  box-shadow:inset 0 1px 0 color-mix(in srgb,#ffffff 18%,transparent);
}
/* ::before is the tile's accent bar, so the sweep rides ::after. */
.tcard.glass::after{
  content:'';position:absolute;top:0;left:-70%;width:45%;height:100%;pointer-events:none;
  background:linear-gradient(100deg, transparent 0%,
             color-mix(in srgb,#ffffff 18%,transparent) 50%, transparent 100%);
  transform:skewX(-18deg);
  animation:tk-sheen 6s cubic-bezier(.4,0,.2,1) infinite;
}
@keyframes tk-sheen{ 0%,68%{ left:-70%; } 100%{ left:140%; } }
@media (prefers-reduced-motion:reduce){
  .tcard.glass::after{ animation:none; display:none; }
  .tcard.glass:hover{ transform:none; }
}

/* ── SolusTV support badge (banner) ──────────────────────────────────────────────────────
   Built from .badge, which already gives the pill and the leading dot; only the behaviour is
   new. The dot breathes while support is open, using the dotPulse keyframes that already exist
   for the sidebar indicators -- one animation, one meaning, everywhere in the panel. */
.sup-badge{text-decoration:none;cursor:pointer;transition:filter .15s ease}
.sup-badge:hover{filter:brightness(1.25)}
.sup-badge.on::before{animation:dotPulse 1.8s ease-in-out infinite}
/* Unread answers waiting. Sits inside the pill so it cannot collide with the clock beside it. */
/* Red, and the same red as the reseller-support badge -- an unread count means the same thing
   wherever it appears, so it must not change colour depending on the pill it sits in. It used to
   inherit currentColor, which inside a green badge painted a dark number on green. */
.sup-count{min-width:16px;height:16px;padding:0 4px;border-radius:999px;
  background:var(--down);color:#fff;font-size:10px;font-weight:800;line-height:16px;
  text-align:center;display:inline-block;font-variant-numeric:tabular-nums;
  box-shadow:0 0 0 1px color-mix(in srgb,var(--down) 45%,transparent)}
@media (max-width:820px){
  /* On a phone the banner is crowded: keep the light, drop the words. */
  .sup-badge span#supBadgeText{display:none}
  .sup-badge{padding:3px 7px}
}
/* The customer's support page: one conversation, so it fills the card rather than sharing it
   with a thread list the way the reseller inbox does. */
.sup-page{display:flex;flex-direction:column;min-height:min(70vh,620px)}
.sup-page .lc-msgs{flex:1 1 auto}
.sup-off{padding:18px;text-align:center;position:sticky;bottom:0;z-index:3;
  background:var(--panel);border-top:1px solid var(--line)}

/* ── the account menu's identity block ───────────────────────────────────────────────────
   These three rules lived INLINE in the customer panel's layout, so the operator panel -- which
   now carries the same menu -- rendered the name and the role run together on one line. One
   definition, both shells; the panel's inline copy is gone rather than left to drift. */
.acct-who{display:flex;align-items:center;gap:11px;padding:12px 13px 11px;
  border-bottom:1px solid var(--line-soft)}
/* The initial, not a photo: there is nowhere in this product to upload one, and a lettered
   disc reads as an identity at a glance where two lines of text read as a paragraph. */
.acct-av{width:34px;height:34px;flex:0 0 auto;border-radius:50%;display:flex;
  align-items:center;justify-content:center;font-size:14px;font-weight:650;
  text-transform:uppercase;color:var(--muted);background:var(--inset);
  border:1px solid var(--line);user-select:none}
/* min-width:0 is what lets the two lines ellipse instead of shoving the disc off the card:
   a flex item's default min-width is auto, so a long name would otherwise refuse to shrink. */
.acct-who-t{min-width:0;flex:1 1 auto}
.acct-who-n{display:block;font-size:13.5px;font-weight:650;color:var(--text);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.acct-who-r{display:block;font-size:11.5px;color:var(--muted);margin-top:2px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   PHONES
   MEASURED 2026-08-26 across all 71 panel pages at 390px. Two things were true: not one page
   pushed the viewport sideways -- .table-wrap has always done its job -- and forty-one of them
   held a table between 1,500 and 2,052px wide inside that scroller. Nothing threw; the panel
   was simply read by dragging a thirteen-column grid past a four-inch window.
   ══════════════════════════════════════════════════════════════════════════════════════════ */

/* ── the toolbar wraps ────────────────────────────────────────────────────────────────────
   .filter-bar already wrapped. .stream-toolbar never did, so on seven pages its right-hand
   buttons were simply cut off the card -- New stream, Import, Export, all of them unreachable
   with no indication they existed. */
@media (max-width:760px){
  .stream-toolbar{ flex-wrap:wrap; row-gap:9px; }
  .stream-search{ flex:1 1 100%; max-width:none; }
  .stream-toolbar .rs-spacer{ display:none; }
}

/* ── a touch target is at least 36px ──────────────────────────────────────────────────────
   Only on a COARSE pointer. A mouse hits a 26px icon button precisely and the desktop density
   is worth keeping; a fingertip is about 9mm and does not. */
@media (pointer:coarse){
  .ic-btn{ min-width:36px; min-height:36px; }
  .btn.btn-sm{ min-height:36px; }
  .acct-item{ padding-top:11px; padding-bottom:11px; }
  /* Measured, not guessed: these were the only classes left under 36px once the icon buttons
     were raised -- the revert arrow beside every setting, and the selection bar's actions. */
  .st-revert, .bulk-clear, .bulk-btn{ min-width:36px; min-height:36px; }
}

/* ── one row, one line ────────────────────────────────────────────────────────────────────
   A card per row was tried and reverted the same day. Turning every cell into its own line
   made a ten-column stream read as ten stacked values, and a list of them read as data piled
   up rather than a table -- which is what it was.

   So the row stays a row and the table scrolls sideways, which is what .table-wrap was built
   for and what it already did before any of this. What is added is the part that was missing:
   nothing inside a cell may wrap. A cell that broke onto a second line made every row a
   different height and the columns stopped lining up down the page, which is the whole reason
   to read a table rather than a list. */
@media (max-width:700px){
  .tbl td, .tbl th, .cf-tbl td, .cf-tbl th{ white-space:nowrap; }
  /* Momentum scrolling, and a scrollbar that is actually visible on a touch device -- a table
     that scrolls with no sign that it does is a table that looks truncated. */
  .table-wrap{ -webkit-overflow-scrolling:touch; scrollbar-width:thin; }
  .table-wrap::-webkit-scrollbar{ height:6px; }
  .table-wrap::-webkit-scrollbar-thumb{ background:var(--line-strong); border-radius:3px; }
}

/* ── a customer's own logo ────────────────────────────────────────────────────────────────
   Both logos are always in the markup and ours is merely hidden, so a customer logo that 404s
   or is blocked falls back by removing ONE class -- no second request, no empty space where a
   brand should be. `!important` because the theme rules that pick between our white and colour
   marks are more specific than this one, and while a customer logo is showing neither of ours
   should be. */
/* The two are MUTUALLY EXCLUSIVE, and that is the whole rule. It used to be one-sided --
   has-brand hid ours, and nothing hid theirs -- so when onerror removed the class (a single
   slow or missed fetch of /brand-logo is enough) ours came back while their image stayed
   visible, and the panel showed both side by side. Now the class decides which of the two is
   displayed, so "both" is not a state that exists. */
.has-brand .logo-dark, .has-brand .logo-light{ display:none !important; }
.brand-custom{ display:none; max-width:100%; height:auto; object-fit:contain; }
.has-brand .brand-custom{ display:block; }

/* CENTRED, and only when a customer logo is showing.
   Our wordmark is wide enough to fill the brand box, so left alignment was never visible on
   it. A customer's mark is usually square, and it sat hard against the left edge looking like
   it had come loose. Centring ours as well would move a logo nobody complained about, so the
   rule is scoped to .has-brand. */
/* When a customer logo shows, make the brand a BLOCK and let the logo FILL the box width. A
   flex item sits at its natural width and never stretches, so max-width alone left a modest
   upload small (pinned to a tiny fixed height before that). width:100% makes it use the space;
   object-fit:contain keeps the aspect ratio so a square/tall mark is contained, never stretched;
   max-height caps how tall it may get. Login has more room than a sidebar, so a taller cap. */
.sb-brand.has-brand, .login-brand.has-brand, .sidebar .brand.has-brand{
  display:block; text-align:center;
}
.sb-brand .brand-custom, .sidebar .brand .brand-custom{
  display:block; margin:0 auto; width:100%; height:auto; max-height:64px; object-fit:contain;
}
.login-brand .brand-custom{
  display:block; margin:0 auto; width:100%; height:auto; max-height:120px; object-fit:contain;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   CARD GRAMMAR — .card.ai-col
   Lifted out of assets/p/ai-services.css, unchanged, because a second page now uses it.
   The names keep the ai- prefix on purpose: renaming them would touch every AI Services
   card for no benefit, and this is where they were born.

   A card lifts on hover and a light sweeps across it once. The sweep is a ::after that
   sits outside the flow and is animation-only, so it costs nothing until a pointer is
   over the card -- and it is turned off entirely for prefers-reduced-motion.
   ══════════════════════════════════════════════════════════════════════════════════════ */
.ai-col{padding:15px 16px !important;min-width:0;position:relative;overflow:hidden;display:flex;flex-direction:column;
  transition:border-color .2s ease, transform .2s ease, box-shadow .2s ease}
.ai-col:hover{transform:translateY(-3px);border-color:color-mix(in srgb,var(--primary,#38cfe6) 40%,var(--line));
  box-shadow:0 1px 0 var(--raise-2,rgba(255,255,255,.08)) inset,0 24px 54px -12px rgba(var(--sh,8 12 20),.6),0 9px 20px rgba(var(--sh,8 12 20),.42)}
.ai-col::after{content:"";position:absolute;top:0;bottom:0;right:0;width:60%;z-index:3;pointer-events:none;border-radius:inherit;
  transform:translateX(160%) skewX(18deg);
  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--primary,#38cfe6) 10%,transparent),rgba(255,255,255,.13),transparent)}
.ai-col:hover::after{animation:aiCardShine .55s ease-out 1}
@keyframes aiCardShine{from{transform:translateX(160%) skewX(18deg)}to{transform:translateX(-220%) skewX(18deg)}}
@media (prefers-reduced-motion:reduce){.ai-col:hover::after{animation:none}}
.ai-card-head{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-bottom:8px}
.ai-card-head h3{margin:0;font-size:14px;font-weight:650;display:flex;align-items:center;gap:7px}
.ai-card-head h3 svg{width:16px;height:16px;color:var(--muted,#8b93a7)}
.ai-pill{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;font-size:11px;font-weight:700;letter-spacing:.02em;border:1px solid var(--line);background:var(--input,rgba(255,255,255,.03));color:var(--muted,#8b93a7)}

/* ── A page the licence does not include ──────────────────────────────────────────────────────
   Shown, not withheld. A trial customer who never sees AI Services or GPU Streams never learns
   that a paid licence has them, so the real page renders and is greyed out with the reason on
   top. `inert` (with pointer-events as the fallback for browsers that predate it) is what makes
   it a picture rather than a control: no click, no focus, no tab stop reaches inside. The page's
   own network calls are cut off in tenant_view() -- greying a live page would leave its loaders
   spinning and its error toasts painting outside this wrapper. */
.trial-lock{ position:relative; }
.trial-lock-note{
  display:flex; gap:12px; align-items:flex-start;
  margin:0 0 18px; padding:14px 16px;
  border:1px solid color-mix(in srgb, var(--warn) 34%, transparent);
  background:color-mix(in srgb, var(--warn) 9%, var(--panel));
  border-radius:var(--radius); color:var(--text); font-size:13.5px; line-height:1.5;
}
.trial-lock-note svg{ width:19px; height:19px; flex:0 0 auto; margin-top:1px; color:var(--warn); }
.trial-lock-note b{ color:var(--warn); }
.trial-lock-note span{ display:block; margin-top:3px; color:var(--muted); font-size:12.5px; }
.trial-lock-body{
  filter:grayscale(1); opacity:.42;
  pointer-events:none; user-select:none;
}
.trial-lock-body [inert]{ pointer-events:none; }
