/* console.css — everything tenger.css (the vendored bundle.css) deliberately
   does not style: the reset it assumes, the <dialog> skin, the JS-invented
   classes, the responsive collapse, and the bespoke product UI (PTT, level
   meter, recorder, zone grid, chips, status cells, history, announcements)
   that has no equivalent in the design system's admin component set. */

/* Four weights of the design system's Gilroy — Medium/Semibold/Bold/Heavy is
   what this console's type scale actually uses; the system publishes
   Thin→Heavy and the rest is deliberately not vendored. font-display: swap
   so the shell never blocks first paint on a font fetch, which matters here
   only for jitter (the files are local, not a network fetch) but costs
   nothing to set correctly. */
@font-face {
  font-family: 'Gilroy';
  src: url('../fonts/Gilroy-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Gilroy';
  src: url('../fonts/Gilroy-Semibold.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Gilroy';
  src: url('../fonts/Gilroy-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Gilroy';
  src: url('../fonts/Gilroy-Heavy.woff2') format('woff2');
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

/* login.html — a separate document (auth.go:51), so it needs its own layout,
   not the shell's. tenger.css has no "down" alert variant (only info/warn/up);
   this adds it with the same pattern rather than inventing a new class shape. */
.tc-alert.down { background: var(--tc-down-soft); border-color: color-mix(in srgb, var(--tc-down) 30%, transparent); color: var(--tc-down); }

.login-shell {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.login-card {
  width: 100%;
  max-width: 360px;
  text-align: center;
}
.login-badge-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: var(--tc-r-md);
  background: var(--tc-brand-soft);
  color: var(--tc-brand);
  margin: 0 auto 12px;
}
.login-form { text-align: left; margin-top: 16px; display: flex; flex-direction: column; gap: 14px; }
.login-submit { width: 100%; display: inline-flex; align-items: center; justify-content: center; gap: 8px; margin-top: 4px; }
.lang-switch { margin: 16px auto 0; width: fit-content; }

/* The sprite <svg> that carries every <symbol> (layout.html, login.html).
   CSP is style-src 'self' — it blocks inline style="" attributes as well as
   third-party stylesheets, so this cannot be a `style` attribute on the
   element; it must be a class. */
.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
/* Same CSP reason: reports.html's CSV download icon reuses icon-upload
   (an up arrow) pointed the other way, rather than drawing a fifth arrow
   glyph for one button. */
/* transform-box: fill-box (not the SVG default, view-box) so rotation pivots
   on the glyph's own 14×14 box — without it the 24×24 viewBox coordinates
   rotate around (0,0) and the whole icon lands outside the visible area. */
.icon-flip { transform-box: fill-box; transform-origin: center; transform: rotate(180deg); }

/* ============================ Reset ============================
   bundle.css assumes these and sets neither — without them the shell sits
   in a default white frame in dark mode, and 11 template uses of [hidden]
   on an element that becomes .tc-field (display:flex, bundle.css:436) would
   stay visible. */
* { box-sizing: border-box; }
body { margin: 0; background: var(--tc-canvas); }
[hidden] { display: none !important; }

/* ============================ Shell ============================ */
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.drawer-toggle { display: none; }
.status-dot-pulse {
  width: 7px; height: 7px; border-radius: 50%; background: currentColor;
  animation: tc-pulse 2s ease-in-out infinite;
}
@keyframes tc-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
.error-banner { margin: 0 32px 16px; }
.lang-switch { margin-top: 10px; }
.logout-form { margin-top: 6px; }
.logout-btn { width: 100%; justify-content: center; }
/* tenger.css's own .tc-scrim (bundle.css:523-528) is built for its modal
   pattern: opacity:0 + visibility:hidden, toggled visible by a `.open`
   class. The drawer here toggles data-drawer-open instead (see
   console.js's initDrawer), which that selector never matches — so
   opacity/visibility must be reset here too, not just display, or the
   scrim stays hit-test-invisible (a click "through" it lands on the page
   content, not the drawer-close handler) even once display:block shows it. */
.tc-scrim { display: none; opacity: 1; visibility: visible; }

/* ============================ Dashboard ============================ */
.dashboard-stats { margin-bottom: 20px; }
/* An active broadcast is the one thing on this page an operator needs to
   notice first — the icon badge switches from the neutral brand tint to
   --tc-up (the same green "streaming"/"online" already means everywhere
   else) only while something is actually live, so it reads as a status,
   not decoration. */
.tc-stat-ic.live { background: var(--tc-up-soft); color: var(--tc-up); }
.dashboard-main-cols { display: grid; grid-template-columns: 1.6fr 1fr; gap: 20px; align-items: start; }
.dashboard-side { display: flex; flex-direction: column; gap: 20px; }
.section-header { align-items: center; justify-content: space-between; }
.section-hint { color: var(--tc-ink-3); }
.zone-form { align-items: end; margin-bottom: 12px; }

.zone-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; margin-top: 16px; }
.zone-card {
  display: block; width: 100%; text-align: left; font: inherit; cursor: pointer;
  /* .tc-card itself has no padding — bundle.css expects a consumer to add
     .tc-card-pad or its own. This is a dense grid tile, not a full section,
     so it gets its own tighter figure rather than the 18px card default. */
  padding: 14px 16px;
  border: 1px solid var(--tc-border); transition: border-color .15s, box-shadow .15s;
}
.zone-card:hover { border-color: var(--tc-brand); }
.zone-card.selected { border-color: var(--tc-brand); box-shadow: var(--tc-ring); }
.zone-card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.zone-name { font: 700 14px/1.3 var(--tc-font); }
.zone-check { display: none; color: var(--tc-brand); }
.zone-card.selected .zone-check { display: inline; }
.zone-meta { display: flex; gap: 10px; }
.status-ok { color: var(--tc-up); }
.status-warn { color: var(--tc-warn); }
.status-bad { color: var(--tc-down); }
.zone-streaming { margin-top: 8px; display: inline-flex; }
.zone-card.streaming { border-color: var(--tc-up); }

.new-devices { padding: var(--tc-card-pad) var(--tc-card-pad) 16px; }
.new-device-list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.new-device {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  border: 1px solid var(--tc-border); border-radius: var(--tc-r-sm); background: var(--tc-surface-2);
}
.status-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; }
.status-dot.online { background: var(--tc-up); }
.new-device-id { font-weight: 700; }
.new-device-ip { color: var(--tc-ink-3); margin-right: auto; }

.zone-settings-list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.zone-settings-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px; border: 1px solid var(--tc-border); border-radius: var(--tc-r-sm);
}
.zone-settings-row > div { display: flex; align-items: center; gap: 8px; }
.zone-master-label { display: flex; align-items: center; gap: 6px; color: var(--tc-ink-3); }
/* Invented at runtime by actions.js:384-397 — no server-side markup, so no
   test can catch these going unstyled. Same visual language as
   .zone-settings-row's checkbox list. */
.zone-edit { display: flex; flex-direction: column; gap: 6px; }
.zone-edit-item { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.btn-small { font-size: 12.5px; padding: 5px 10px; }

.page-panel, .announce-panel { display: flex; flex-direction: column; }
.page-panel > .tc-card-pad, .announce-panel > .tc-card-pad { display: flex; flex-direction: column; gap: 14px; }
.panel-header { display: flex; align-items: center; gap: 12px; }
.panel-subtitle { color: var(--tc-ink-3); }
.ptt-btn-wrapper { display: flex; justify-content: center; }
.ptt-button {
  width: 100%; min-height: 96px; flex-direction: column; gap: 4px;
  touch-action: none; user-select: none;
}
.ptt-button.ptt-live { background: var(--tc-down); border-color: var(--tc-down); }
#page-panel.live { box-shadow: 0 0 0 2px var(--tc-down); }
.ptt-hint { opacity: .85; font-weight: 500; }
.level-meter-wrap { padding: 0 2px; }
.level-meter { height: 6px; border-radius: 999px; background: var(--tc-surface-3); overflow: hidden; }
.level-fill { height: 100%; width: 0; background: var(--tc-up); transition: width .08s linear; }

.announce-files-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.announce-files-head h3 { margin-right: auto; }
.file-list { list-style: none; margin: 0; padding: 0; max-height: 220px; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; }
.file-item {
  display: flex; align-items: center; gap: 8px; padding: 8px 10px;
  border-radius: var(--tc-r-sm); cursor: pointer;
}
.file-item:hover { background: var(--tc-surface-2); }
.file-duration { margin-left: auto; color: var(--tc-ink-3); }
.zone-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.announce-actions { display: flex; gap: 10px; }
.announce-actions .tc-btn { flex: 1; }

.active-announcements { margin-top: 4px; border-top: 1px solid var(--tc-border); padding-top: 12px; }
.active-announcements ul { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.ann {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 10px; border-radius: var(--tc-r-sm); background: var(--tc-surface-2);
}
.ann-emergency { background: var(--tc-down-soft); color: var(--tc-down); }
.ann-info { display: flex; align-items: center; gap: 8px; }

/* ============================ Schedule form ============================
   .field (aliased to .tc-field) only sets the 7px gap between ITS OWN label
   and input — nothing separated one .field block from the next, so "Бус",
   "Давтамж" and "Цаг" ran straight into each other with no breathing room
   between sections, on top of .tc-label's own 12.5px size (a bundle.css
   constant used everywhere else too) making the whole form read as
   squeezed rather than merely compact. */
.schedule-form { display: flex; flex-direction: column; gap: 20px; }

/* ============================ Chips ============================
   The hidden checkbox/radio input is why .chip-on is the only selection
   cue — there is no other way to see what a chip means. */
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  display: inline-flex; align-items: center; padding: 7px 14px; border-radius: var(--tc-r-pill);
  border: 1px solid var(--tc-border); font: 600 13px/1 var(--tc-font); cursor: pointer;
  background: var(--tc-surface); color: var(--tc-ink-2); transition: background .15s, border-color .15s, color .15s;
}
.chip input { display: none; }
.chip:hover { border-color: var(--tc-brand); }
.chip-on { background: var(--tc-brand); border-color: var(--tc-brand); color: #fff; }
.day-grid { max-width: 420px; }
.tz-note { margin-top: 4px; }

/* ============================ Recorder ============================ */
.record-box { display: flex; flex-direction: column; gap: 6px; }
.record-row { display: flex; align-items: center; gap: 10px; }
.record-hint { color: var(--tc-ink-3); }
.btn-record.recording { background: var(--tc-down); border-color: var(--tc-down); color: #fff; }

/* ============================ Devices table ============================ */
.provision-panel { margin-bottom: 16px; }
.status-cell { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; }
.dot-online { background: var(--tc-up); }
.dot-offline { background: var(--tc-ink-4); }
.last-seen { margin-left: 4px; }
.device-name { margin-left: 6px; color: var(--tc-ink-3); }
.row-offline { opacity: .6; }
.volume-cell { display: flex; align-items: center; gap: 8px; }
/* Six actions in one cell: labelled buttons push the row wide enough to
   force the whole table into horizontal scroll even on a desktop viewport,
   which is what made the row look cut off. Icon-only, with the label kept
   as both the title tooltip and a visually-hidden <span> (so a screen
   reader still gets it), keeps every row a fixed, scannable width. */
/* Icon-only (the label lives in a visually-hidden <span> below) keeps each
   button small, but wrapping seven of them into a fixed max-width squeezed
   them into an ugly two-line cluster instead — worse than the horizontal
   scroll it was meant to avoid. One row, no wrap; .tc-table-wrap's own
   overflow-x: auto (tenger.css) is what actually handles a row that does
   not fit, by scrolling the whole table, not by breaking one cell's icons
   into their own mini grid. */
.actions-cell { display: flex; flex-wrap: nowrap; gap: 4px; }
.actions-cell .tc-btn { padding: 7px; flex: 0 0 auto; }
.actions-cell .tc-btn span {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.playing-tag { color: var(--tc-up); display: inline-flex; }

/* ============================ Firmware panel ============================ */
.fw-head { display: flex; align-items: center; gap: 10px; }
.fw-head h2 { margin-right: auto; }
.fw-list { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.fw-item { display: flex; align-items: center; gap: 12px; padding: 8px 10px; border: 1px solid var(--tc-border); border-radius: var(--tc-r-sm); }
.fw-pick { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.fw-meta { margin-left: auto; margin-right: 8px; color: var(--tc-ink-3); }

/* ============================ Dialogs ============================
   bundle.css's modal is a .tc-scrim + .tc-modal div pair, always visible
   (display:flex — the wrapping .tc-scrim's opacity/visibility is what hides
   it) since it assumes the pair is only ever mounted while shown. These
   dialogs are native <dialog> elements instead (showModal()/close() in
   actions.js), so bundle.css's unconditional display:flex must be undone
   for the closed state, or a dialog with no [open] attribute — the UA
   default is display:none — renders anyway. The [open] rule then restores
   the flex layout .tc-modal-body/-head/-foot expect. */
dialog.tc-modal {
  display: none;
  border: none; border-radius: var(--tc-r-lg); box-shadow: var(--tc-shadow-lg);
  padding: 24px; max-width: 480px; width: calc(100% - 32px);
  background: var(--tc-surface); color: var(--tc-ink);
}
dialog.tc-modal[open] { display: flex; flex-direction: column; gap: 12px; }
dialog.tc-modal::backdrop { background: rgba(10, 20, 35, .5); }
.dialog-close { position: absolute; top: 14px; right: 14px; }
.provision-dialog, .device-settings-dialog, .device-delete-dialog { position: relative; }
.dialog-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 16px; }
.dialog-quick-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; }
.zone-fieldset { border: 1px solid var(--tc-border); border-radius: var(--tc-r-sm); padding: 10px; margin: 10px 0; }
.zone-checks { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.check { display: flex; align-items: center; gap: 8px; }

/* ============================ Schedules ============================ */
.schedule-list { display: flex; flex-direction: column; gap: 8px; }
.schedule-item {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 12px; border: 1px solid var(--tc-border); border-radius: var(--tc-r-sm); flex-wrap: wrap;
}
.schedule-item.off { opacity: .55; }
.schedule-main { display: flex; flex-direction: column; gap: 2px; }
.schedule-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.history-list { display: flex; flex-direction: column; gap: 6px; }
.history-item {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  border-radius: var(--tc-r-sm); background: var(--tc-surface-2); flex-wrap: wrap;
}
.badge-fired, .badge-live { background: var(--tc-up-soft); color: var(--tc-up); }
.badge-skipped { background: var(--tc-warn-soft); color: var(--tc-warn); }
.badge-error { background: var(--tc-down-soft); color: var(--tc-down); }
.history-reason { color: var(--tc-ink-3); }

/* ============================ Reports ============================ */
.report-filter { align-items: end; margin-bottom: 16px; }
.report-table td, .report-table th { white-space: nowrap; }

/* ============================ Utility ============================ */
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.muted { color: var(--tc-ink-3); }
.error-text { color: var(--tc-down); }
.error-text:empty { display: none; }

/* ============================ Responsive ============================
   ≥1180px is the default (full sidebar). 900–1180px collapses to the
   published rail; the shell's data-collapsed drives that, set by a small
   inline listener in console.js is out of scope here — the CSS-only floor
   is the off-canvas drawer below 900px, which needs no script to be safe. */
@media (max-width: 1180px) and (min-width: 900px) {
  .tc-app:not([data-collapsed]) .tc-sidebar { width: var(--tc-sidebar-w-collapsed); flex-basis: var(--tc-sidebar-w-collapsed); }
  .tc-app:not([data-collapsed]) .tc-brand-name,
  .tc-app:not([data-collapsed]) .tc-nav-item span,
  .tc-app:not([data-collapsed]) .tc-user-meta { display: none; }
  .tc-app:not([data-collapsed]) .tc-nav-item { justify-content: center; padding: 10px; }
}
@media (max-width: 899px) {
  .tc-sidebar {
    position: fixed; inset: 0 auto 0 0; transform: translateX(-100%);
    transition: transform .2s ease; box-shadow: var(--tc-shadow-lg);
  }
  .app[data-drawer-open] .tc-sidebar { transform: translateX(0); }
  .app[data-drawer-open] .tc-scrim { display: block; position: fixed; inset: 0; background: rgba(10,20,35,.4); z-index: 25; }
  .drawer-toggle { display: grid; }
  .tc-content { padding: 18px 16px 32px; }
  .dashboard-main-cols { grid-template-columns: 1fr; }
  .tc-table-wrap { overflow-x: auto; }
  .ptt-button { min-height: 72px; }
}
@media (max-width: 359px) {
  html, body { overflow-x: hidden; }
}
