/* IPTV Auth admin interface.
   One stylesheet, no framework, no external fonts: this UI has to work on an
   isolated network and while the thing it manages is broken. */

/* Alteox brand palette, from the press kit. Kept as its own layer so the
   semantic variables below can be read against it, and so nothing has to guess
   at a hex code twice. */
:root {
  --brand-light-blue: #92DAEB;
  --brand-medium-blue: #6ABDD8;
  --brand-blue: #3880BB;
  --brand-bright-blue: #107CB5;
  --brand-purple: #293C9F;
  --brand-pink-purple: #8D63EE;
  --brand-dark-blue: #062535;
  --brand-dark: #060D11;
}

/* Semantic layer, matching alteox.com: white ground, near-black body text,
   brand blue for headings and links, and the dark navy reserved for the one
   primary action on a page -- the same role "Contact Us" plays on the site. */
:root {
  --bg: #ffffff;
  --panel: #ffffff;
  --panel-2: #f2f6f9;
  --border: #dfe7ed;
  --text: #171717;
  --muted: #5c6f7c;

  /* Links, headings and the active navigation item. */
  --accent: var(--brand-blue);
  /* The primary button. */
  --cta: var(--brand-dark-blue);
  --cta-text: #ffffff;
  --accent-text: #ffffff;

  --ok: #1a7f4b;
  --ok-bg: #e6f5ec;
  --warn: #8a5a00;
  --warn-bg: #fdf3e0;
  --err: #b5262f;
  --err-bg: #fdeaec;

  --radius: 12px;
  /* Buttons and chips are pills on the site, so they are pills here. */
  --radius-pill: 999px;
  --shadow: 0 1px 2px rgba(6, 37, 53, .04), 0 8px 24px rgba(6, 37, 53, .05);

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  /* The site sets "creato"; Creato Display is the licensed family name. Neither
     is bundled -- this UI loads no external fonts, because it has to work on an
     isolated network -- so both are named ahead of the system stack. */
  --sans: "Creato Display", creato, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Built up from the brand darks rather than a neutral grey, so the dark
       theme reads as the same family as the light one. */
    --bg: var(--brand-dark);
    --panel: #0c1a23;
    --panel-2: #12252f;
    --border: #1d3a49;
    --text: #e6eef2;
    --muted: #8fa5b1;
    --accent: var(--brand-medium-blue);
    --accent-text: var(--brand-dark);
    /* On a dark ground the navy CTA would disappear, so the light blue carries
       it instead and the text goes dark. */
    --cta: var(--brand-medium-blue);
    --cta-text: var(--brand-dark);
    --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25);
    --ok: #56d68f;
    --ok-bg: #14301f;
    --warn: #e5b567;
    --warn-bg: #33280f;
    --err: #ef8a92;
    --err-bg: #38191c;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  background-image: linear-gradient(180deg, #f3f8fb 0%, var(--bg) 420px);
  background-repeat: no-repeat;
  -webkit-text-size-adjust: 100%;
}

@media (prefers-color-scheme: dark) {
  body { background-image: linear-gradient(180deg, #0b1c26 0%, var(--bg) 420px); }
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ------------------------------------------------------------------ layout */

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

.sidebar {
  width: 220px;
  flex: 0 0 220px;
  background: var(--panel-2);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
}

.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 18px 16px 14px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
}
.brand:hover { text-decoration: none; }

/* The logo is used as supplied and never recoloured or rearranged, per the brand
   guidelines, so it is sized by height alone and left to keep its own ratio. */
.brand-logo {
  height: 26px;
  width: auto;
  display: block;
}

/* "Auth" names this tool next to the company mark, rather than being set into
   the logo itself. */
.brand-sub {
  font-weight: 600;
  font-size: 13px;
  letter-spacing: .02em;
  color: var(--muted);
}

/* The supplied light variant is greyscale, so on a dark background the full
   colour mark is kept and only lifted slightly off the panel. */
@media (prefers-color-scheme: dark) {
  .brand-logo { filter: saturate(1.1) brightness(1.12); }
}

.nav { list-style: none; margin: 0; padding: 10px 8px; flex: 1; overflow-y: auto; }
.nav li { margin: 1px 0; }
.nav a {
  display: block;
  padding: 7px 12px;
  border-radius: var(--radius-pill);
  color: var(--text);
  font-weight: 500;
}
.nav a:hover { background: var(--panel-2); text-decoration: none; }
.nav a.active { background: var(--accent); color: var(--accent-text); }
.nav-sep { height: 1px; background: var(--border); margin: 10px 6px; }

.sidebar-foot { padding: 12px; border-top: 1px solid var(--border); }
.who { display: block; margin-bottom: 8px; color: var(--text); }
.who:hover { text-decoration: none; }
.who-name {
  display: block;
  font-size: 12px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.content { flex: 1; padding: 24px 28px 60px; min-width: 0; max-width: 1400px; }

.centered {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

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

.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
h1 {
  font-size: 24px;
  margin: 0 0 4px;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -.01em;
  color: var(--accent);
}
h2 { font-size: 15px; margin: 0 0 10px; font-weight: 700; }
h3 { font-weight: 700; }
.subtitle { color: var(--muted); margin: 0; font-size: 13px; }
.actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ------------------------------------------------------------------ panels */

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 18px;
  margin-bottom: 16px;
}
.panel-tight { padding: 0; overflow: hidden; }
.panel h2 { margin-top: 0; }
.panel-note { color: var(--muted); font-size: 13px; margin: 0 0 12px; }

.grid { display: grid; gap: 16px; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.grid-stats { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }

.stat {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 16px 18px;
}
.stat-value { color: var(--accent); }
.stat-value { font-size: 24px; font-weight: 650; font-variant-numeric: tabular-nums; }
.stat-label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }

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

.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: top; }
th { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); font-weight: 600; white-space: nowrap; }
tbody tr:hover { background: var(--panel-2); }
tbody tr:last-child td { border-bottom: none; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.nowrap, th.nowrap { white-space: nowrap; }
.mono { font-family: var(--mono); font-size: 12px; }
.empty { padding: 28px 16px; text-align: center; color: var(--muted); }

/* A quieter aside inside a heading or a label -- the word that says what the
   value beside it is, rather than being the value. */
.meta { color: var(--muted); font-weight: 400; }

/* --------------------------------------------------------------- team rows */

/* One collapsed row per team member. <details> is used rather than a scripted
   accordion because the pages carry no inline script under their CSP, and this
   needs none: the browser does the toggling. */
.member {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  margin-bottom: 8px;
}
.member[open] { border-color: var(--accent); }

.member > summary {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 14px;
  cursor: pointer;
  list-style: none;          /* Firefox */
}
.member > summary::-webkit-details-marker { display: none; }
.member > summary:hover { background: var(--panel-2); border-radius: var(--radius); }

.member-who { display: flex; align-items: center; gap: 8px; min-width: 0; }
.member-who strong { overflow-wrap: anywhere; }
.member-badges { display: flex; gap: 6px; flex-wrap: wrap; }

/* Pushed to the right on a wide screen, and simply wraps on a narrow one. */
.member-meta { margin-left: auto; color: var(--muted); font-size: 12px; white-space: nowrap; }

.member-toggle {
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
  padding: 2px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
}
.member[open] .member-toggle { color: var(--muted); }
.member[open] .member-toggle::after { content: " \2013 close"; }

.member-body { padding: 4px 14px 14px; border-top: 1px solid var(--border); margin-top: 2px; }

.member-danger {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px dashed var(--border);
}
.member-danger .hint { margin: 0; }

/* The role key above the list: two columns, badge then meaning. */
.roles-key td { border: 0; padding: 3px 10px 3px 0; vertical-align: top; font-size: 13px; }
.roles-key td:first-child { width: 116px; white-space: nowrap; }
.roles-key td:last-child { color: var(--muted); }
.roles-key td:last-child strong { color: var(--text); }

/* A heading that divides a page into sections, below the h1 but above a panel. */
.section-head { font-size: 15px; margin: 22px 0 4px; }
.section-note { margin: 0 0 8px; }

/* ------------------------------------------------------------------ badges */

.badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: 600;
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--border);
  white-space: nowrap;
}
.badge.ok { background: var(--ok-bg); color: var(--ok); border-color: transparent; }
.badge.warn { background: var(--warn-bg); color: var(--warn); border-color: transparent; }
.badge.err { background: var(--err-bg); color: var(--err); border-color: transparent; }
.badge.role { text-transform: capitalize; }
/* Server kind next to a mapping, so "which of these is MediaMTX" is readable
   without knowing the naming convention someone used for their servers.
   Composes with .ok/.warn/.err, which set only colour -- so a row of server
   labels keeps one size and one typeface and varies only in state. */
.badge.kind { font-family: var(--mono); font-weight: 500; font-size: 10px; }
/* Several of these sit in one cell, so they need to breathe without the template
   inserting whitespace that a mono font would render as a wide gap. */
.badge.kind + .badge.kind { margin-left: 3px; }

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

label { display: block; font-weight: 550; margin-bottom: 4px; font-size: 13px; }
.hint { color: var(--muted); font-weight: 400; font-size: 12px; margin-top: 3px; }

/* Styled by exclusion rather than by listing the types we know about.
   
   The list was the bug, twice. An attribute selector matches the attribute and not the
   property, so <input name=x> matched nothing and rendered as a white box in a dark
   interface; that was fixed by adding :not([type]). Then a type="url" field did the same
   thing, because url was never in the list either -- and the next one to be added would
   have done it again. Naming what must NOT look like a text field is a short, closed list;
   naming everything that should is an open one that quietly loses. */
input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=submit]):not([type=button]):not([type=reset]):not([type=range]):not([type=color]):not([type=image]):not([type=hidden]),
select, textarea {
  width: 100%;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
  font: inherit;
}
textarea { min-height: 70px; resize: vertical; font-family: var(--mono); font-size: 12px; }
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}

.field { margin-bottom: 14px; }
.field-row { display: flex; gap: 12px; flex-wrap: wrap; }
.field-row > .field { flex: 1 1 200px; }
.field-inline { display: flex; gap: 8px; align-items: center; }
.field-inline input[type=checkbox] { width: auto; margin: 0; }
.field-inline label { margin: 0; font-weight: 500; }

fieldset { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; margin: 0 0 14px; }
legend { padding: 0 6px; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); font-weight: 600; }

.checklist {
  /* Tall enough to choose from without scrolling for a typical line-up. It grows with the
     viewport rather than being a fixed number of pixels, because the useful height is
     "as much of the list as fits" -- 260px showed about eight channels of a hundred, so
     building a playlist meant scrolling a small window. */
  max-height: min(60vh, 620px);
  min-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  background: var(--panel);
}
.checklist label {
  display: flex;
  gap: 8px;
  align-items: baseline;
  font-weight: 450;
  padding: 3px 0;
  margin: 0;
}
.checklist input { width: auto; margin: 0; }
.checklist .meta { color: var(--muted); font-size: 12px; }

/* ----------------------------------------------------------------- buttons */

.btn {
  display: inline-block;
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-weight: 550;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.btn:hover { background: var(--panel-2); text-decoration: none; }
.btn-primary { background: var(--cta); border-color: var(--cta); color: var(--cta-text); }
.btn-primary:hover { filter: brightness(1.12); background: var(--cta); }
.btn-danger { color: var(--err); border-color: var(--err); background: transparent; }
.btn-danger:hover { background: var(--err-bg); }
.btn-quiet { color: var(--muted); font-weight: 500; }
.btn-sm { padding: 3px 9px; font-size: 12px; }
.btn-block { display: block; width: 100%; }
.btn:disabled { opacity: .5; cursor: not-allowed; }

/* Padded to the same inset as a panel's fields, so the primary button lines up with the
   inputs it applies to instead of with the page edge. */
.btn-row {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 16px;
  padding: 0 18px;
}
.btn-row .spacer { flex: 1; }

/* ------------------------------------------------------------------ alerts */

.alert { padding: 10px 14px; border-radius: var(--radius); margin-bottom: 16px; font-weight: 500; }
.alert-ok { background: var(--ok-bg); color: var(--ok); }
.alert-err { background: var(--err-bg); color: var(--err); }
.alert-warn { background: var(--warn-bg); color: var(--warn); }

/* ------------------------------------------------------------------ filters */

.filters { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 14px; }
.filters .field { margin: 0; }
.filters input[type=search] { min-width: 220px; }
.filters select { min-width: 130px; }

/* ------------------------------------------------------------- bulk actions */

.bulkbar {
  display: none;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 12px;
  margin-bottom: 12px;
}
.bulkbar.on { display: flex; }
.bulkbar .count { font-weight: 600; }
.bulkbar select, .bulkbar input { width: auto; }

/* -------------------------------------------------------------- code blocks */

pre.snippet {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.55;
  margin: 0 0 10px;
  white-space: pre;
}
.secret-once {
  background: var(--warn-bg);
  color: var(--warn);
  border-radius: var(--radius);
  padding: 12px 14px;
  margin-bottom: 14px;
}
.secret-once code {
  display: block;
  font-family: var(--mono);
  font-size: 13px;
  margin-top: 6px;
  overflow-wrap: anywhere;
  user-select: all;
}

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

.login-card { width: 100%; max-width: 360px; }
/* The sign-in card is the one place with room to give the mark some presence, so
   it is centred and larger than the sidebar copy. */
.login-card .brand {
  border: none;
  padding: 0 0 18px;
  justify-content: center;
}
.login-card .brand-logo { height: 38px; }

/* -------------------------------------------------------------- pagination */

.pager { display: flex; gap: 8px; align-items: center; justify-content: space-between; margin-top: 14px; }
.pager .info { color: var(--muted); font-size: 12px; }

/* ------------------------------------------------------------------ mobile */

@media (max-width: 800px) {
  .shell { flex-direction: column; }
  .sidebar { width: auto; flex: none; height: auto; position: static; border-right: none; border-bottom: 1px solid var(--border); }
  .nav { display: flex; flex-wrap: wrap; }
  .nav-sep { display: none; }
  .content { padding: 16px; }
}

/* ------------------------------------------------------------- logo picker */

.logo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 12px;
}

.logo-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  background: var(--panel);
}
.logo-card-selected { border-color: var(--accent); }

/* Channel logos are nearly all transparent, and they come in two kinds: white
   lettering meant for a dark background, and dark lettering meant for a light one.
   Either one is invisible against a tile of its own colour, so the preview is a
   chequerboard -- which shows the transparency -- and can be flipped between dark and
   light, because no single choice can display both.

   Dark by default: broadcaster logos are overwhelmingly the white-on-transparent
   kind, and this interface is dark. */
.logo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 84px;
  margin-bottom: 8px;
  border-radius: var(--radius);
  background-color: #1b2b34;
  background-image:
    linear-gradient(45deg, #243b47 25%, transparent 25%, transparent 75%, #243b47 75%),
    linear-gradient(45deg, #243b47 25%, transparent 25%, transparent 75%, #243b47 75%);
  background-size: 16px 16px;
  background-position: 0 0, 8px 8px;
}
.logo-preview img { max-width: 100%; max-height: 76px; object-fit: contain; }

/* Flipped by the button above the grid, for a logo drawn in dark ink. */
body.logos-on-light .logo-preview {
  background-color: #ffffff;
  background-image:
    linear-gradient(45deg, #e2e8ec 25%, transparent 25%, transparent 75%, #e2e8ec 75%),
    linear-gradient(45deg, #e2e8ec 25%, transparent 25%, transparent 75%, #e2e8ec 75%);
}

/* In the light theme the sensible default is the other way round. */
@media (prefers-color-scheme: light) {
  .logo-preview {
    background-color: #ffffff;
    background-image:
      linear-gradient(45deg, #e2e8ec 25%, transparent 25%, transparent 75%, #e2e8ec 75%),
      linear-gradient(45deg, #e2e8ec 25%, transparent 25%, transparent 75%, #e2e8ec 75%);
  }
  body.logos-on-light .logo-preview {
    background-color: #1b2b34;
    background-image:
      linear-gradient(45deg, #243b47 25%, transparent 25%, transparent 75%, #243b47 75%),
      linear-gradient(45deg, #243b47 25%, transparent 25%, transparent 75%, #243b47 75%);
  }
}

.logo-meta { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }
.logo-card .hint { margin: 0; }
.logo-card .btn-row { margin-top: 8px; gap: 6px; }

/* A panel for something an operator should look at but that is not an error. The
   guide fed by two sources is the case this exists for: the listings are correct,
   and the configuration is still not what anyone intended. */
.panel-warn {
  border-color: var(--warn);
  background: var(--warn-bg);
}
.panel-warn h2 {
  margin-top: 0;
  color: var(--warn);
  font-size: 1rem;
}

/* A list that reads as prose rather than as a bulleted list, for short enumerations
   inside a panel that is already making a point in sentences. */
ul.plain {
  list-style: none;
  margin: 0.6rem 0;
  padding: 0;
}
ul.plain li {
  padding: 0.15rem 0;
}

/* A row for something deliberately switched off. Dimmed rather than hidden: an
   operator needs to see that they ignored a feed channel, or the decision becomes
   invisible and looks like a channel the feed stopped carrying. */
.row-muted td { opacity: 0.55; }

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

/* An editor that opens as its own full-width row beneath the record it edits.
   The alternative was a form inside a cell, which stretches the row to the height of
   the form and squeezes the fields into one column's width -- so the fields drift away
   from the row they belong to and nothing lines up. */
.rowform > td { padding: 0; background: var(--panel-2); }
.rowform-body {
  padding: 16px 18px;
  border-left: 3px solid var(--accent);
}
.rowform-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-size: 13px;
}
/* The footer keeps the switch that decides whether the device runs next to the button
   that saves it, rather than leaving it among the text fields where it reads as one more
   detail. */
.rowform-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding-top: 6px;
  border-top: 1px solid var(--border);
}
.rowform-actions { display: flex; gap: 8px; }
/* Deleting sits apart from saving, so the two are not adjacent buttons on one line. */
.rowform-danger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px dashed var(--border);
}
.btn.btn-danger { color: var(--err); border-color: var(--err); }
.btn.btn-danger:hover { background: var(--err-bg); }

/* A value meant to be copied whole -- an endpoint, a key -- needs the width to show it.
   At a field's natural width these read as truncated strings nobody trusts. */
.input-wide { width: 100%; }

/* A search box with its own clear button, for a filter that submits to the server: the
   browser's native × on a search input only empties the field, which leaves the page still
   showing the filtered result. This one is a link back to the unfiltered page. */
.input-clearable { position: relative; display: block; }
.input-clearable input { padding-right: 30px; }
.input-clear {
  position: absolute;
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  text-decoration: none;
}
.input-clear:hover { color: var(--fg); }
