/* Kanade Client App — dashboard UI.
   Header + left sidebar nav + switchable content views. Design tokens
   are CSS custom properties so light / dark only re-points a handful of
   variables (see the :root / prefers-color-scheme blocks). */

:root {
  /* `color-scheme` makes the native UA chrome (scrollbars, form
     controls, the scrollbar corner) follow the theme — without it the
     WebView paints a light scrollbar even under the dark palette below,
     which reads as a stray white bar. The dark block re-points it. */
  color-scheme: light;

  --bg: #f4f4f6;
  --panel: #ffffff;
  --panel-2: #f7f7f9;
  --border: #e3e3e8;
  --text: #1a1a1a;
  --muted: #6b6b72;
  --accent: #5b3a8c; /* baton violet */
  --accent-soft: rgba(91, 58, 140, 0.1);
  --ok: #2e7d32;
  --warn: #8a6d00;
  --fail: #b00020;
  --sidebar-w: 188px;
  --radius: 10px;
  /* Custom scrollbar thumb — a soft translucent track-on-content look so
     it blends into either palette instead of the chunky default bar. */
  --scrollbar-thumb: rgba(0, 0, 0, 0.24);
  --scrollbar-thumb-hover: rgba(0, 0, 0, 0.4);
}

* {
  box-sizing: border-box;
}

/* ---- Scrollbars ----
   Gecko-style props (Firefox) plus the WebKit/Blink pseudo-elements so
   both the macOS WKWebView and the Windows WebView2 (Chromium) host get
   a thin themed bar. `color-scheme` (above) already fixes the native
   color; these just slim it down and round it off. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) transparent;
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background-color: var(--scrollbar-thumb);
  border-radius: 999px;
  /* Transparent border + padding-box clip insets the thumb so it reads as
     a slim pill with breathing room rather than a full-width slab. */
  border: 2px solid transparent;
  background-clip: padding-box;
}

::-webkit-scrollbar-thumb:hover {
  /* `background-color`, not the `background` shorthand — the shorthand
     would reset `background-clip` back to border-box and lose the inset. */
  background-color: var(--scrollbar-thumb-hover);
}

::-webkit-scrollbar-corner {
  background: transparent;
}

/* `[hidden]` must win over the explicit `display` on components like
   .badge / .dash-card / .view (an author `display` otherwise overrides
   the UA `[hidden]{display:none}` rule and leaks "0" badges / empty
   cards). */
[hidden] {
  display: none !important;
}

html,
body {
  height: 100%;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Yu Gothic UI",
    Meiryo, Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
}

#app {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

/* ---- Header ---- */
.app-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 48px;
  padding: 0 16px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.02em;
}

/* タクト (baton) mark — the inline SVG sits on transparent; the
   gradient lives on the baton path itself, so no background tile. */
.brand-mark {
  display: block;
  width: 22px;
  height: 22px;
  flex: none;
}

/* Connection status dot + label. */
.conn {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  color: var(--muted);
}

.conn-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #9aa;
  box-shadow: 0 0 0 0 transparent;
}

.conn-connected .conn-dot {
  background: #2e9d4f;
  box-shadow: 0 0 0 3px rgba(46, 157, 79, 0.18);
}

.conn-connecting .conn-dot {
  background: #c9a227;
  box-shadow: 0 0 0 3px rgba(201, 162, 39, 0.18);
}

.conn-disconnected .conn-dot {
  background: #c64141;
  box-shadow: 0 0 0 3px rgba(198, 65, 65, 0.18);
  animation: conn-pulse 1.2s ease-in-out infinite;
}

@keyframes conn-pulse {
  50% {
    opacity: 0.4;
  }
}

/* ---- Layout: sidebar + content ---- */
.layout {
  flex: 1 1 auto;
  display: flex;
  min-height: 0;
}

.sidebar {
  flex: 0 0 var(--sidebar-w);
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px 10px;
  background: var(--panel);
  border-right: 1px solid var(--border);
  overflow-y: auto;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  font-size: 13.5px;
  font-weight: 500;
  text-align: left;
  color: var(--text);
  background: transparent;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
}

.nav-item:hover {
  background: var(--panel-2);
}

.nav-item.active {
  background: var(--accent-soft);
  color: var(--accent);
}

.nav-item .nav-icon {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  color: currentColor;
}

.nav-label {
  flex: 1 1 auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav-count {
  font-size: 12px;
}

.nav-badge {
  flex: 0 0 auto;
}

.nav-sep {
  height: 1px;
  margin: 8px 6px;
  background: var(--border);
}

.nav-error {
  padding: 8px 10px;
  font-size: 12px;
}

/* Client build version pinned to the sidebar foot. `margin-top: auto`
   pushes it below the nav items in the flex column; dim + small so it's
   an unobtrusive "what's running" readout (mirrors the SPA sidebar).
   Starts `hidden` in the markup and is revealed by main.ts once filled
   (robust against an HTML formatter breaking a `:empty` rule). */
.sidebar-version {
  /* `margin-top: auto` drops it to the bottom when the nav doesn't fill
     the column; `position: sticky; bottom: 0` keeps it pinned to the
     viewport bottom if the nav ever overflows and scrolls (the job
     categories are injected, so the count isn't fixed). The panel
     background stops scrolling nav items showing through. */
  margin-top: auto;
  position: sticky;
  bottom: 0;
  padding: 8px 10px 2px;
  background: var(--panel);
  font-size: 10px;
  color: var(--muted);
  opacity: 0.7;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Content column: the scrolling active view stacked above the fixed
   実行状況 dock. As a flex column it lets .content take the slack and the
   dock keep its own bounded height, so the dock is always flush to the
   window bottom (no sticky-on-short-page gap). */
.main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.content {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  padding: 20px 24px;
  overflow-y: auto;
}

.view-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 16px;
  font-weight: 700;
  margin: 0 0 14px;
}

.view[hidden] {
  display: none;
}

/* ---- Dashboard cards ---- */
.dash-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
}

.dash-card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px;
  text-align: left;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}

.dash-card-static {
  cursor: default;
}

button.dash-card:hover {
  border-color: var(--accent);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
}

.dash-icon {
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  color: var(--accent);
}

.dash-card-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.dash-card-label {
  font-size: 12px;
  color: var(--muted);
}

.dash-card-value {
  font-size: 16px;
  font-weight: 700;
}

.dash-card.ok .dash-card-value {
  color: var(--ok);
}
.dash-card.warn .dash-card-value {
  color: var(--warn);
}
.dash-card.fail .dash-card-value {
  color: var(--fail);
}

/* ---- Health checks (#290) ----
   Every health element (.health-hero, .health-chip, .check-card, the
   badges) reads its accent from a single `--status` custom property set
   by the `.status-{ok,warn,fail,unknown}` class. So a row's color is
   declared once, and the tinted badge / left rail / chip all stay in
   sync. Soft fills use color-mix over `--status` (WebView2 + WKWebView
   both ship a recent enough Blink/WebKit for it). */
.status-ok {
  --status: var(--ok);
}
.status-warn {
  --status: var(--warn);
}
.status-fail {
  --status: var(--fail);
}
.status-unknown {
  --status: var(--muted);
}

/* Hero summary: one prominent roll-up card above the per-check list —
   status glyph in a tinted disc, headline + online/version sub-line, and
   a count-chip strip on the right. The left rail is the status color. */
.health-hero {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 2px 0 16px;
  padding: 16px 18px;
  background: linear-gradient(
    to right,
    color-mix(in srgb, var(--status) 9%, var(--panel)),
    var(--panel) 60%
  );
  border: 1px solid var(--border);
  border-left: 4px solid var(--status);
  border-radius: var(--radius);
}

.health-badge {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  color: var(--status);
  background: color-mix(in srgb, var(--status) 16%, transparent);
}

.health-badge svg {
  width: 24px;
  height: 24px;
}

.health-hero-body {
  flex: 1 1 auto;
  min-width: 0;
}

.health-hero-title {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
}

.health-hero-sub {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 2px 0 0;
  font-size: 12.5px;
  color: var(--muted);
}

.health-sep {
  opacity: 0.5;
}

/* Online indicator: a small dot + label, green when connected, muted
   when not. Mirrors the header conn-dot vocabulary at text scale. */
.health-online {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.health-online-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--muted);
}

.health-online.is-online {
  color: var(--ok);
}

.health-online.is-online .health-online-dot {
  background: var(--ok);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 22%, transparent);
}

/* Count-chip strip — one pill per non-empty status bucket. */
.health-stats {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.health-chip {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  font-size: 12px;
  font-weight: 700;
  color: var(--status);
  background: color-mix(in srgb, var(--status) 14%, transparent);
  border-radius: 999px;
  white-space: nowrap;
}

/* Per-check cards: gapped rounded rows (not hairline-divided) with a
   status left rail + tinted glyph badge. */
ul.checks {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0;
}

.check-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--status);
  border-radius: 10px;
  transition:
    border-color 0.15s ease,
    box-shadow 0.15s ease;
}

.check-card:hover {
  border-color: color-mix(in srgb, var(--status) 45%, var(--border));
  box-shadow: 0 2px 12px -6px rgba(0, 0, 0, 0.28);
}

.check-badge {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  color: var(--status);
  background: color-mix(in srgb, var(--status) 15%, transparent);
}

.check-badge svg {
  width: 18px;
  height: 18px;
}

.check-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 auto;
}

.check-name {
  font-weight: 600;
}

.check-detail {
  color: var(--muted);
  font-size: 13px;
}

.fix-btn {
  flex: 0 0 auto;
  margin-left: auto;
  padding: 5px 14px;
  font-size: 13px;
}

.fix-btn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ---- Job list (#291) ---- */
.job-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}

.job-row:first-child {
  border-top: 0;
}

.job-icon {
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  color: var(--accent);
  object-fit: contain;
}

.job-name {
  font-weight: 500;
}

.job-desc {
  flex: 1 1 100%;
  margin-left: 32px;
  color: var(--muted);
  font-size: 13px;
}

.job-run-btn {
  margin-left: auto;
  padding: 5px 16px;
  font-size: 13px;
}

/* ---- Run status dock (#291) ----
   A flex-fixed region at the bottom of the content column (see .main): a
   fixed header above an internally-scrolling row list. Being a flex
   sibling of .content — not `position: sticky` *inside* it — the dock
   always sits flush to the window bottom (sticky only pins on overflow,
   so on a short page it used to float up mid-column leaving a gap), and
   its scrollbar lives in its own region instead of nesting with the
   view's. */
.runs-panel {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Cap the dock height so a busy panel scrolls its rows internally
     rather than eating the whole window; the view above keeps the slack.
     A soft top shadow lifts it off the rows above. */
  max-height: 45vh;
  background: var(--panel);
  border-top: 1px solid var(--border);
  box-shadow: 0 -10px 18px -10px rgba(0, 0, 0, 0.3);
}

.runs-panel[hidden] {
  display: none;
}

/* Fixed dock header: the 実行状況 title + a live 実行中 count. It stays put
   while only the row list below scrolls, so the count is always visible. */
.runs-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 24px 8px;
}

.runs-title {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
}

/* 実行中 count pill — accent-tinted (work in progress), deliberately NOT
   the red --fail of the unread-notifications badge. Hidden at zero. */
.runs-active-badge {
  background: var(--accent);
  color: #fff;
}

/* The dock's one scrolling region: the run rows. */
.runs-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 0 24px 16px;
}

/* Brief highlight when a new run is registered — positive "it started"
   feedback so the operator doesn't second-guess and re-press 実行. */
.runs-panel.runs-flash {
  animation: runs-flash 1.2s ease-out;
}

@keyframes runs-flash {
  0%,
  30% {
    background: color-mix(in srgb, var(--accent) 12%, var(--panel));
  }
  100% {
    background: var(--panel);
  }
}

/* A run row is a column: a header line (icon / name / status / kill or
   chevron) above its output block. */
.run-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 0;
  border-top: 1px solid var(--border);
}

.run-row:first-child {
  border-top: 0;
}

.run-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
}

/* Completed rows collapse their output behind a click on the header. */
.run-head[data-run-toggle] {
  cursor: pointer;
  user-select: none;
}

.run-icon {
  flex: 0 0 auto;
}

.run-name {
  font-weight: 500;
}

.run-status {
  flex: 1 1 auto;
}

.kill-btn {
  margin-left: auto;
  padding: 4px 10px;
  font-size: 13px;
}

/* Disclosure chevron on collapsible (completed-with-output) rows; rotates
   when the row is expanded. Mirrors the notifications panel affordance. */
.run-chevron {
  margin-left: auto;
  flex: 0 0 auto;
  align-self: center;
  font-size: 12px;
  color: var(--muted);
  transition: transform 0.15s ease;
}

.run-row.expanded .run-chevron {
  transform: rotate(90deg);
}

/* Output flows inside the dock's scroll (no inner scrollbar of its own —
   that nested third bar was the readability complaint). */
.run-output {
  margin: 0;
  padding: 8px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 12px;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.run-row.status-completed .run-status {
  color: var(--ok);
}
.run-row.status-failed .run-status,
.run-row.status-killed .run-status {
  color: var(--fail);
}

/* ---- Notifications (#102) ---- */
.badge {
  display: inline-block;
  min-width: 18px;
  padding: 0 6px;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  color: #fff;
  background: var(--fail);
  border-radius: 999px;
  vertical-align: middle;
}

.notif-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}

.notif-row:first-child {
  border-top: 0;
}

.notif-icon {
  flex: 0 0 auto;
  line-height: 1.4;
}

.notif-main {
  flex: 1 1 auto;
  min-width: 0;
}

.notif-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

/* The title row is the click target that expands/collapses the body. */
.notif-toggle {
  cursor: pointer;
  user-select: none;
}

/* Hover feedback so the row visibly reads as "click to expand". */
.notif-toggle:hover .notif-chevron {
  color: var(--accent);
}

.notif-head.notif-toggle:hover .notif-title {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

/* Unread marker — a small dot before the title; cleared once read/acked. */
.notif-dot {
  flex: 0 0 auto;
  align-self: center;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}

.notif-row.priority-warn .notif-dot {
  background: var(--warn);
}

.notif-row.priority-emergency .notif-dot {
  background: var(--fail);
}

.notif-title {
  font-weight: 600;
}

.notif-prio {
  font-size: 12px;
}

/* Chevron pushed to the right of the title row; rotates when expanded. */
.notif-chevron {
  margin-left: auto;
  flex: 0 0 auto;
  align-self: center;
  font-size: 14px;
  color: var(--muted);
  transition:
    transform 0.15s ease,
    color 0.15s ease;
}

.notif-row.expanded .notif-chevron {
  transform: rotate(90deg);
}

/* Collapsed one-line body preview — the at-a-glance signal that this
   notification HAS a body and that the row expands to show it. Clipped to a
   single line; hidden once the full text is revealed. */
.notif-preview {
  margin: 3px 0 0;
  color: var(--muted);
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}

.notif-row.expanded .notif-preview {
  display: none;
}

/* Body + meta collapse away until the row is expanded. */
.notif-collapse {
  display: none;
}

.notif-row.expanded .notif-collapse {
  display: block;
}

.notif-text {
  margin: 2px 0 0;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.notif-meta {
  margin: 4px 0 0;
  font-size: 12px;
}

.notif-row.acked {
  opacity: 0.6;
}

.notif-row.priority-warn .notif-title {
  color: var(--warn);
}

.notif-row.priority-emergency .notif-title {
  color: var(--fail);
}

.notif-row.notif-flash {
  animation: notif-flash 2s ease-out;
}

@keyframes notif-flash {
  0%,
  40% {
    background: rgba(176, 0, 32, 0.18);
  }
  100% {
    background: transparent;
  }
}

.notif-ack-btn {
  flex: 0 0 auto;
  padding: 4px 12px;
  font-size: 13px;
}

/* 確認 is GATED on reading the body: until the row is opened it renders as a
   softer secondary "開いて確認" button (it just expands the row), so it never
   looks like the real confirm action. Once read it becomes the solid 確認. */
.notif-ack-locked,
.notif-ack-locked:hover {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

.notif-acked {
  flex: 0 0 auto;
  font-size: 12px;
}

/* All notifications surface as native OS toasts (#102/#635) — no in-app
   toast or blocking-modal styles live here anymore. */

/* ---- Shared ---- */
.muted {
  color: var(--muted);
}

.error {
  color: var(--fail);
}

button {
  padding: 6px 14px;
  font-size: 14px;
  background: var(--accent);
  color: #fff;
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}

button:hover {
  background: #4a2f73;
}

/* Reset for the structural buttons that aren't "primary" actions. */
.nav-item,
.dash-card {
  background: transparent;
}

button.nav-item:hover,
button.dash-card:hover {
  background: var(--panel-2);
}

button.nav-item.active:hover {
  background: var(--accent-soft);
}

/* ---- Dark theme ---- */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    --bg: #17171a;
    --panel: #1f1f23;
    --panel-2: #26262b;
    --border: #33333a;
    --text: #f0f0f2;
    --muted: #a2a2ab;
    --accent: #b89be0;
    --accent-soft: rgba(184, 155, 224, 0.16);
    --ok: #7cc47f;
    --warn: #e0b341;
    --fail: #ff6b6b;
    --scrollbar-thumb: rgba(255, 255, 255, 0.22);
    --scrollbar-thumb-hover: rgba(255, 255, 255, 0.38);
  }

  button:hover {
    background: #9f7fd0;
  }

  .badge {
    background: #b00020;
  }
}
