<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Agent Safety Net Dashboard</title>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link
    href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Instrument+Sans:wght@400;600;700&display=swap"
    rel="stylesheet">
  <style>
    :root {
      --bg-deep: #080c14;
      --bg-surface: #0f1520;
      --bg-raised: #151d2b;
      --bg-inset: #0a0f18;
      --border: #1e2a3e;
      --border-subtle: #172030;
      --text-primary: #d0d8e8;
      --text-muted: #8a9bb4;
      --text-dim: #627890;
      --accent-cyan: #00d4ff;
      --accent-cyan-dim: rgba(0, 212, 255, 0.12);
      --accent-amber: #ffb347;
      --accent-amber-dim: rgba(255, 179, 71, 0.12);
      --accent-red: #ff4757;
      --accent-red-dim: rgba(255, 71, 87, 0.12);
      --accent-green: #2ed573;
      --accent-green-dim: rgba(46, 213, 115, 0.12);
      --accent-lavender: #b8a0e0;
      --accent-sky: #6bc5e8;
      --accent-peach: #e8a87c;
      --font-data: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
      --font-label: 'Instrument Sans', -apple-system, sans-serif;
      --radius: 6px;
      --radius-lg: 10px;
    }

    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background: var(--bg-deep);
      color: var(--text-primary);
      font-family: var(--font-data);
      font-size: 13px;
      padding: 28px 32px;
      background-image:
        repeating-linear-gradient(90deg, rgba(0, 212, 255, 0.02) 0px, rgba(0, 212, 255, 0.02) 1px, transparent 1px, transparent 40px),
        repeating-linear-gradient(0deg, rgba(0, 212, 255, 0.02) 0px, rgba(0, 212, 255, 0.02) 1px, transparent 1px, transparent 40px);
      min-height: 100vh;
    }

    /* ── Header ── */
    h1 {
      font-family: var(--font-label);
      font-size: 18px;
      font-weight: 700;
      color: var(--text-primary);
      margin-bottom: 28px;
      display: flex;
      align-items: center;
      gap: 12px;
      letter-spacing: 0.3px;
      animation: fadeSlideIn 0.4s ease-out;
    }

    h1 span.live {
      color: var(--accent-cyan);
      font-family: var(--font-data);
      font-weight: 500;
      font-size: 12px;
      display: flex;
      align-items: center;
      gap: 6px;
    }

    /* ── View Toggle ── */
    .view-toggle {
      margin-left: auto;
      display: flex;
      gap: 0;
      border: 1px solid var(--border);
      border-radius: 20px;
      overflow: hidden;
      background: var(--bg-surface);
    }

    .view-toggle button {
      background: transparent;
      color: var(--text-muted);
      border: none;
      padding: 7px 18px;
      font-size: 11px;
      cursor: pointer;
      font-family: var(--font-label);
      font-weight: 600;
      text-transform: uppercase;
      letter-spacing: 0.8px;
      transition: all 0.2s;
    }

    .view-toggle button.active {
      background: var(--accent-cyan);
      color: var(--bg-deep);
      box-shadow: 0 0 12px rgba(0, 212, 255, 0.25);
    }

    .view-toggle button:hover:not(.active) {
      background: var(--bg-raised);
      color: var(--text-primary);
    }

    /* ── Grid / Cards ── */
    .grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 14px;
      margin-bottom: 24px;
    }

    .grid-4 {
      grid-template-columns: repeat(4, 1fr);
    }

    .card {
      background: var(--bg-surface);
      border: 1px solid var(--border-subtle);
      border-radius: var(--radius-lg);
      padding: 20px 22px;
      position: relative;
      overflow: hidden;
      animation: fadeSlideIn 0.5s ease-out both;
    }

    .card::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      height: 1px;
      background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.15), transparent);
    }

    .card:nth-child(1) {
      animation-delay: 0.05s;
    }

    .card:nth-child(2) {
      animation-delay: 0.1s;
    }

    .card:nth-child(3) {
      animation-delay: 0.15s;
    }

    .card:nth-child(4) {
      animation-delay: 0.2s;
    }

    .card-label {
      font-family: var(--font-label);
      font-size: 11px;
      font-weight: 600;
      color: var(--accent-sky);
      text-transform: uppercase;
      letter-spacing: 1.5px;
      margin-bottom: 10px;
    }

    .card-value {
      font-size: 30px;
      font-weight: 700;
      font-variant-numeric: tabular-nums;
    }

    .card-value.small {
      font-size: 22px;
    }

    .cost {
      color: var(--accent-cyan);
    }

    .calls {
      color: var(--text-primary);
    }

    .green {
      color: var(--accent-green);
    }

    /* ── Badges ── */
    .badge {
      display: inline-block;
      padding: 4px 14px;
      border-radius: 20px;
      font-family: var(--font-label);
      font-weight: 700;
      font-size: 12px;
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }

    .badge-sm {
      padding: 2px 8px;
      font-size: 9px;
      border-radius: 10px;
      letter-spacing: 0.8px;
    }

    .badge-pass {
      background: var(--accent-green-dim);
      color: var(--accent-green);
      box-shadow: 0 0 8px rgba(46, 213, 115, 0.15);
    }

    .badge-flag {
      background: var(--accent-amber-dim);
      color: var(--accent-amber);
      box-shadow: 0 0 8px rgba(255, 179, 71, 0.15);
    }

    .badge-block {
      background: var(--accent-red-dim);
      color: var(--accent-red);
      box-shadow: 0 0 8px rgba(255, 71, 87, 0.15);
    }

    /* ── Sections ── */
    .section {
      background: var(--bg-surface);
      border: 1px solid var(--border-subtle);
      border-radius: var(--radius-lg);
      padding: 22px 24px;
      margin-bottom: 14px;
      animation: fadeSlideIn 0.5s ease-out both;
    }

    .section:nth-of-type(1) {
      animation-delay: 0.15s;
    }

    .section:nth-of-type(2) {
      animation-delay: 0.2s;
    }

    .section:nth-of-type(3) {
      animation-delay: 0.25s;
    }

    .section:nth-of-type(4) {
      animation-delay: 0.3s;
    }

    .section:nth-of-type(5) {
      animation-delay: 0.35s;
    }

    .section h2 {
      font-family: var(--font-label);
      font-size: 12px;
      font-weight: 600;
      color: var(--accent-sky);
      margin-bottom: 14px;
      text-transform: uppercase;
      letter-spacing: 1.8px;
      padding-left: 12px;
      border-left: 2px solid var(--accent-cyan);
    }

    /* ── Signals ── */
    .signal {
      padding: 10px 14px;
      border-radius: var(--radius);
      margin-bottom: 6px;
      font-size: 13px;
      display: flex;
      gap: 10px;
      align-items: center;
      transition: background 0.15s;
    }

    .signal-high {
      background: var(--accent-red-dim);
      border-left: 3px solid var(--accent-red);
      box-shadow: inset 4px 0 12px -4px rgba(255, 71, 87, 0.1);
    }

    .signal-medium {
      background: var(--accent-amber-dim);
      border-left: 3px solid var(--accent-amber);
      box-shadow: inset 4px 0 12px -4px rgba(255, 179, 71, 0.1);
    }

    .signal-low {
      background: var(--accent-cyan-dim);
      border-left: 3px solid var(--accent-cyan);
      box-shadow: inset 4px 0 12px -4px rgba(0, 212, 255, 0.1);
    }

    .sev {
      font-family: var(--font-label);
      font-weight: 700;
      font-size: 10px;
      text-transform: uppercase;
      letter-spacing: 0.8px;
      min-width: 52px;
    }

    .sev-high {
      color: var(--accent-red);
    }

    .sev-medium {
      color: var(--accent-amber);
    }

    .sev-low {
      color: var(--accent-cyan);
    }

    .score-badge {
      font-size: 11px;
      color: var(--accent-lavender);
      font-weight: 400;
    }

    /* ── Call Timeline / Spans ── */
    .span-table {
      width: 100%;
      border-collapse: separate;
      border-spacing: 0 3px;
    }

    .span-table td {
      padding: 8px 14px;
      font-size: 13px;
      background: var(--bg-inset);
      vertical-align: middle;
    }

    .span-table tr:nth-child(even) td {
      background: rgba(15, 21, 32, 0.6);
    }

    .span-table tr:hover td {
      background: var(--bg-raised);
    }

    .span-table td:first-child {
      border-radius: var(--radius) 0 0 var(--radius);
    }

    .span-table td:last-child {
      border-radius: 0 var(--radius) var(--radius) 0;
      text-align: right;
    }

    .span-model {
      color: var(--accent-cyan);
      font-weight: 500;
    }

    .span-time {
      color: var(--accent-sky);
      font-size: 11px;
      white-space: nowrap;
    }

    .span-dur {
      color: var(--accent-lavender);
      font-variant-numeric: tabular-nums;
    }

    .span-cost {
      color: var(--accent-amber);
      font-size: 12px;
      opacity: 0.9;
    }

    .span-status {
      text-align: center;
    }

    .empty {
      color: var(--text-muted);
      font-style: italic;
      font-size: 13px;
    }

    /* ── Pulse / Live Indicator ── */
    .pulse {
      animation: glowPulse 2.5s ease-in-out infinite;
    }

    @keyframes glowPulse {

      0%,
      100% {
        opacity: 1;
        text-shadow: 0 0 6px rgba(0, 212, 255, 0.4);
      }

      50% {
        opacity: 0.5;
        text-shadow: 0 0 2px rgba(0, 212, 255, 0.1);
      }
    }

    /* ── Entrance animation ── */
    @keyframes fadeSlideIn {
      from {
        opacity: 0;
        transform: translateY(8px);
      }

      to {
        opacity: 1;
        transform: translateY(0);
      }
    }

    /* ── Reduced Motion ── */
    @media (prefers-reduced-motion: reduce) {

      *,
      *::before,
      *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
      }
    }

    /* ── Footer ── */
    .footer {
      margin-top: 32px;
      padding-top: 16px;
      border-top: 1px solid var(--border-subtle);
      text-align: center;
      color: var(--text-muted);
      font-size: 11px;
      font-family: var(--font-label);
      letter-spacing: 0.5px;
    }

    /* ── Bars ── */
    .bar-container {
      height: 6px;
      background: var(--bg-inset);
      border-radius: 3px;
      overflow: hidden;
      margin-top: 8px;
    }

    .bar-fill {
      height: 100%;
      border-radius: 3px;
      transition: width 0.5s ease;
    }

    .bar-green {
      background: linear-gradient(90deg, var(--accent-green), #34e87f);
    }

    .bar-yellow {
      background: linear-gradient(90deg, var(--accent-amber), #ffc46b);
    }

    .bar-red {
      background: linear-gradient(90deg, var(--accent-red), #ff6b7a);
    }

    /* ── Metrics ── */
    .metric-row {
      display: flex;
      justify-content: space-between;
      align-items: baseline;
      margin-bottom: 6px;
    }

    .metric-label {
      color: var(--accent-sky);
      font-size: 13px;
    }

    .metric-value {
      color: var(--text-primary);
      font-size: 13px;
      font-weight: 700;
    }

    .metric-value.good {
      color: var(--accent-green);
    }

    .metric-value.warn {
      color: var(--accent-amber);
    }

    .metric-value.bad {
      color: var(--accent-red);
    }

    /* ── Compliance ── */
    .compliance-item {
      padding: 12px 16px;
      background: var(--bg-inset);
      border-radius: var(--radius);
      margin-bottom: 6px;
      border: 1px solid var(--border-subtle);
    }

    .compliance-status {
      display: flex;
      align-items: center;
      gap: 10px;
      margin-bottom: 4px;
    }

    .dot {
      width: 7px;
      height: 7px;
      border-radius: 50%;
      display: inline-block;
    }

    .dot-green {
      background: var(--accent-green);
      box-shadow: 0 0 6px rgba(46, 213, 115, 0.4);
    }

    .dot-yellow {
      background: var(--accent-amber);
      box-shadow: 0 0 6px rgba(255, 179, 71, 0.4);
    }

    .dot-red {
      background: var(--accent-red);
      box-shadow: 0 0 6px rgba(255, 71, 87, 0.4);
    }

    .hidden {
      display: none;
    }

    /* ── Filter Bar ── */
    .filter-bar {
      display: flex;
      gap: 6px;
      margin-bottom: 14px;
      flex-wrap: wrap;
    }

    .filter-btn {
      background: var(--bg-inset);
      color: var(--text-muted);
      border: 1px solid var(--border);
      padding: 5px 12px;
      border-radius: 16px;
      font-size: 10px;
      cursor: pointer;
      font-family: var(--font-label);
      font-weight: 600;
      text-transform: uppercase;
      letter-spacing: 0.8px;
      transition: all 0.15s;
    }

    .filter-btn.active {
      background: var(--accent-cyan-dim);
      color: var(--accent-cyan);
      border-color: rgba(0, 212, 255, 0.3);
    }

    .filter-btn:hover:not(.active) {
      background: var(--bg-raised);
      color: var(--text-primary);
      border-color: var(--border);
    }

    /* ── Call Groups ── */
    .call-group {
      margin-bottom: 10px;
    }

    .call-group-header {
      font-family: var(--font-label);
      font-size: 11px;
      font-weight: 600;
      color: var(--accent-cyan);
      padding: 4px 0;
      cursor: pointer;
      display: flex;
      align-items: center;
      gap: 6px;
      transition: color 0.15s;
    }

    .call-group-header:hover {
      color: #44e0ff;
    }

    .call-group-arrow {
      font-size: 10px;
      transition: transform 0.2s;
    }

    .call-group-arrow.collapsed {
      transform: rotate(-90deg);
    }

    .call-group-body {
      margin-left: 4px;
    }

    /* ── Zero State ── */
    .zero-state {
      text-align: center;
      padding: 44px 20px;
    }

    .zero-state-icon {
      font-size: 48px;
      margin-bottom: 14px;
    }

    .zero-state-text {
      font-family: var(--font-label);
      font-size: 16px;
      color: var(--accent-green);
      font-weight: 700;
    }

    .zero-state-sub {
      font-size: 13px;
      color: var(--accent-sky);
      margin-top: 6px;
    }

    /* ── Entity Rows ── */
    .entity-row {
      padding: 10px 14px;
      background: var(--bg-inset);
      border-radius: var(--radius);
      margin-bottom: 4px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .entity-name {
      color: var(--text-primary);
      font-size: 13px;
    }

    .entity-detail {
      color: var(--accent-peach);
      font-size: 12px;
    }

    .entity-cost {
      color: var(--accent-cyan);
      font-size: 13px;
      font-weight: 700;
    }

    .session-info {
      font-size: 12px;
      color: var(--accent-sky);
      margin-bottom: 16px;
      font-family: var(--font-label);
    }

    /* ── Confidence ── */
    .confidence-pct {
      font-size: 12px;
      font-weight: 700;
      margin-left: auto;
    }

    .confidence-high {
      color: var(--accent-red);
    }

    .confidence-med {
      color: var(--accent-amber);
    }

    .confidence-low {
      color: var(--accent-green);
    }

    /* ── Review Buttons ── */
    .review-btns {
      display: flex;
      gap: 4px;
      margin-left: 8px;
    }

    .review-btn {
      padding: 3px 10px;
      font-size: 10px;
      border-radius: 12px;
      border: none;
      cursor: pointer;
      font-family: var(--font-label);
      font-weight: 600;
      letter-spacing: 0.3px;
      transition: opacity 0.15s, transform 0.1s;
    }

    .review-btn:hover {
      opacity: 0.85;
      transform: scale(1.02);
    }

    .review-btn-confirm {
      background: var(--accent-red);
      color: #fff;
    }

    .review-btn-dismiss {
      background: var(--accent-green);
      color: #fff;
    }

    /* ── Safety Toggle ── */
    .safety-toggle {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-left: 16px;
    }

    .toggle-switch {
      position: relative;
      width: 40px;
      height: 20px;
      background: var(--accent-red);
      border-radius: 10px;
      cursor: pointer;
      transition: background 0.3s, box-shadow 0.3s;
      box-shadow: 0 0 8px rgba(255, 71, 87, 0.25);
    }

    .toggle-switch.on {
      background: var(--accent-green);
      box-shadow: 0 0 8px rgba(46, 213, 115, 0.25);
    }

    .toggle-switch::after {
      content: '';
      position: absolute;
      top: 2px;
      left: 2px;
      width: 16px;
      height: 16px;
      background: #fff;
      border-radius: 50%;
      transition: transform 0.3s;
    }

    .toggle-switch.on::after {
      transform: translateX(20px);
    }

    .toggle-label {
      font-family: var(--font-label);
      font-size: 11px;
      font-weight: 600;
      color: var(--text-primary);
      text-transform: uppercase;
      letter-spacing: 0.8px;
    }

    /* ── Policies (natural-language rules; API: /dashboard/api/custom-policies) ── */
    .policies-list {
      display: flex;
      flex-direction: column;
      gap: 6px;
    }

    .policy-item {
      background: var(--bg-inset);
      border: 1px solid var(--border-subtle);
      border-radius: var(--radius);
      padding: 14px 18px;
      display: grid;
      grid-template-columns: 1fr auto;
      grid-template-rows: auto auto auto;
      gap: 4px 16px;
      align-items: center;
      transition: border-color 0.2s, background 0.2s;
      position: relative;
    }

    .policy-item:hover {
      border-color: var(--border);
      background: rgba(15, 21, 32, 0.8);
    }

    .policy-item.disabled-policy {
      opacity: 0.45;
    }

    .policy-item.disabled-policy:hover {
      opacity: 0.6;
    }

    .policy-name {
      font-family: var(--font-label);
      font-size: 13px;
      font-weight: 700;
      color: var(--text-primary);
      letter-spacing: 0.2px;
    }

    .policy-rule {
      font-size: 12px;
      color: var(--text-muted);
      grid-column: 1;
      line-height: 1.5;
      white-space: pre-wrap;
      word-break: break-word;
    }

    .policy-tags {
      display: flex;
      flex-wrap: wrap;
      gap: 6px;
      align-items: center;
      grid-column: 1;
      margin-top: 2px;
    }

    .policy-tag {
      display: inline-block;
      padding: 3px 10px;
      border-radius: 6px;
      font-family: var(--font-label);
      font-size: 10px;
      font-weight: 600;
      letter-spacing: 0.45px;
      text-transform: uppercase;
    }

    .policy-tag-scope {
      background: rgba(0, 212, 255, 0.08);
      color: var(--accent-cyan);
      border: 1px solid rgba(0, 212, 255, 0.25);
    }

    .policy-tag-sev-low {
      background: var(--accent-green-dim);
      color: var(--accent-green);
      border: 1px solid rgba(46, 213, 115, 0.35);
    }

    .policy-tag-sev-medium {
      background: var(--accent-amber-dim);
      color: var(--accent-amber);
      border: 1px solid rgba(255, 179, 71, 0.35);
    }

    .policy-tag-sev-high {
      background: var(--accent-red-dim);
      color: var(--accent-red);
      border: 1px solid rgba(255, 71, 87, 0.35);
    }

    .policy-actions {
      grid-row: 1 / 4;
      grid-column: 2;
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .policy-toggle-mini {
      position: relative;
      width: 34px;
      height: 18px;
      background: var(--accent-red);
      border-radius: 9px;
      cursor: pointer;
      transition: background 0.3s, box-shadow 0.3s;
      box-shadow: 0 0 6px rgba(255, 71, 87, 0.2);
      flex-shrink: 0;
    }

    .policy-toggle-mini.on {
      background: var(--accent-green);
      box-shadow: 0 0 6px rgba(46, 213, 115, 0.2);
    }

    .policy-toggle-mini::after {
      content: '';
      position: absolute;
      top: 2px;
      left: 2px;
      width: 14px;
      height: 14px;
      background: #fff;
      border-radius: 50%;
      transition: transform 0.25s;
    }

    .policy-toggle-mini.on::after {
      transform: translateX(16px);
    }

    .policy-action-btn {
      background: none;
      border: 1px solid var(--border);
      color: var(--text-dim);
      width: 28px;
      height: 28px;
      border-radius: 6px;
      cursor: pointer;
      font-size: 13px;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.15s;
      flex-shrink: 0;
    }

    .policy-action-btn:hover {
      border-color: var(--accent-cyan);
      color: var(--accent-cyan);
      background: var(--accent-cyan-dim);
    }

    .policy-action-btn.delete:hover {
      border-color: var(--accent-red);
      color: var(--accent-red);
      background: var(--accent-red-dim);
    }

    .add-policy-btn {
      background: var(--bg-inset);
      border: 1px dashed var(--border);
      border-radius: var(--radius);
      padding: 14px 18px;
      color: var(--text-dim);
      font-family: var(--font-label);
      font-size: 12px;
      font-weight: 600;
      cursor: pointer;
      display: flex;
      align-items: center;
      gap: 8px;
      letter-spacing: 0.4px;
      transition: all 0.2s;
    }

    .add-policy-btn:hover {
      border-color: var(--accent-cyan);
      color: var(--accent-cyan);
      background: rgba(0, 212, 255, 0.04);
    }

    .add-policy-btn .plus-icon {
      width: 22px;
      height: 22px;
      border: 1px solid currentColor;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 14px;
      font-weight: 300;
    }

    /* Policy form (inline) */
    .policy-form {
      background: var(--bg-raised);
      border: 1px solid var(--accent-cyan);
      border-radius: var(--radius-lg);
      padding: 20px 22px;
      box-shadow: 0 0 20px rgba(0, 212, 255, 0.06), 0 8px 32px rgba(0, 0, 0, 0.2);
      animation: fadeSlideIn 0.25s ease-out;
    }

    .policy-form-title {
      font-family: var(--font-label);
      font-size: 12px;
      font-weight: 600;
      color: var(--accent-cyan);
      text-transform: uppercase;
      letter-spacing: 1.2px;
      margin-bottom: 14px;
    }

    .policy-form-group {
      margin-bottom: 12px;
    }

    .policy-form-label {
      font-family: var(--font-label);
      font-size: 10px;
      font-weight: 600;
      color: var(--text-muted);
      text-transform: uppercase;
      letter-spacing: 1px;
      margin-bottom: 5px;
      display: block;
    }

    .policy-form-input {
      width: 100%;
      background: var(--bg-inset);
      border: 1px solid var(--border);
      color: var(--text-primary);
      padding: 9px 14px;
      border-radius: var(--radius);
      font-family: var(--font-data);
      font-size: 13px;
      transition: border-color 0.2s;
    }

    .policy-form-input:focus {
      border-color: var(--accent-cyan);
      outline: none;
      box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.08);
    }

    .policy-form-textarea {
      width: 100%;
      background: var(--bg-inset);
      border: 1px solid var(--border);
      color: var(--text-primary);
      padding: 9px 14px;
      border-radius: var(--radius);
      font-family: var(--font-data);
      font-size: 12px;
      line-height: 1.6;
      min-height: 64px;
      resize: vertical;
      transition: border-color 0.2s;
    }

    .policy-form-textarea:focus {
      border-color: var(--accent-cyan);
      outline: none;
      box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.08);
    }

    .policy-form-btns {
      display: flex;
      gap: 8px;
      margin-top: 4px;
    }

    .policy-form-save {
      background: var(--accent-cyan);
      color: var(--bg-deep);
      border: none;
      padding: 7px 18px;
      border-radius: 16px;
      font-family: var(--font-label);
      font-size: 11px;
      font-weight: 700;
      cursor: pointer;
      letter-spacing: 0.3px;
      transition: opacity 0.15s, box-shadow 0.15s;
      box-shadow: 0 0 10px rgba(0, 212, 255, 0.15);
    }

    .policy-form-save:hover {
      opacity: 0.9;
      box-shadow: 0 0 16px rgba(0, 212, 255, 0.25);
    }

    .policy-form-save:disabled {
      opacity: 0.35;
      cursor: not-allowed;
    }

    .policy-form-cancel {
      background: transparent;
      color: var(--text-muted);
      border: 1px solid var(--border);
      padding: 7px 18px;
      border-radius: 16px;
      font-family: var(--font-label);
      font-size: 11px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.15s;
    }

    .policy-form-cancel:hover {
      color: var(--text-primary);
      border-color: var(--text-muted);
    }

    .policy-empty {
      text-align: center;
      padding: 28px 16px;
      color: var(--text-dim);
      font-family: var(--font-label);
      font-size: 13px;
      letter-spacing: 0.2px;
    }

    .policy-empty-hint {
      font-size: 11px;
      color: var(--text-dim);
      margin-top: 6px;
      opacity: 0.7;
    }

    /* ── Setup Overlay ── */
    .setup-overlay {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(8, 12, 20, 0.92);
      backdrop-filter: blur(8px);
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 1000;
    }

    .setup-card {
      position: relative;
      background: var(--bg-surface);
      border: 1px solid var(--border);
      border-radius: var(--radius-lg);
      padding: 36px;
      max-width: 560px;
      width: 90%;
      box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5), 0 0 40px rgba(0, 212, 255, 0.04);
    }

    .setup-close {
      position: absolute;
      top: 12px;
      right: 14px;
      background: transparent;
      border: none;
      color: var(--text-muted);
      font-size: 22px;
      line-height: 1;
      cursor: pointer;
      padding: 4px 8px;
      border-radius: 4px;
      transition: color 0.15s, background 0.15s;
    }

    .setup-close:hover {
      color: var(--text-primary);
      background: rgba(255, 255, 255, 0.06);
    }

    .setup-card h2 {
      font-family: var(--font-label);
      color: var(--text-primary);
      font-size: 18px;
      margin-bottom: 16px;
    }

    .setup-card p {
      color: var(--text-muted);
      font-size: 14px;
      margin-bottom: 16px;
    }

    .setup-options {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 12px;
      margin-bottom: 20px;
    }

    @media (max-width: 640px) {
      .setup-options {
        grid-template-columns: 1fr;
      }
    }

    .setup-option {
      background: var(--bg-inset);
      border: 2px solid var(--border);
      border-radius: var(--radius);
      padding: 18px;
      cursor: pointer;
      text-align: center;
      transition: border-color 0.2s, background 0.2s;
    }

    .setup-option:hover {
      border-color: var(--accent-cyan);
      background: var(--bg-raised);
    }

    .setup-option.selected {
      border-color: var(--accent-cyan);
      background: var(--accent-cyan-dim);
    }

    .setup-option h3 {
      font-family: var(--font-label);
      color: var(--text-primary);
      font-size: 14px;
      margin-bottom: 4px;
    }

    .setup-option p {
      color: var(--text-muted);
      font-size: 12px;
      margin: 0;
    }

    .setup-input {
      width: 100%;
      background: var(--bg-inset);
      border: 1px solid var(--border);
      color: var(--text-primary);
      padding: 10px 14px;
      border-radius: var(--radius);
      font-family: var(--font-data);
      font-size: 13px;
      margin-bottom: 8px;
      transition: border-color 0.2s;
    }

    .setup-input:focus {
      border-color: var(--accent-cyan);
      outline: none;
      box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.1);
    }

    .setup-btn {
      background: var(--accent-cyan);
      color: var(--bg-deep);
      border: none;
      padding: 9px 22px;
      border-radius: 20px;
      font-family: var(--font-label);
      font-size: 13px;
      font-weight: 700;
      cursor: pointer;
      letter-spacing: 0.3px;
      transition: opacity 0.15s, box-shadow 0.15s;
      box-shadow: 0 0 12px rgba(0, 212, 255, 0.2);
    }

    .setup-btn:hover {
      opacity: 0.9;
      box-shadow: 0 0 20px rgba(0, 212, 255, 0.3);
    }

    .setup-btn:disabled {
      opacity: 0.4;
      cursor: not-allowed;
    }

    .setup-code {
      background: var(--bg-inset);
      border: 1px solid var(--border);
      border-radius: var(--radius);
      padding: 12px 16px;
      font-family: var(--font-data);
      font-size: 12px;
      color: var(--accent-cyan);
      position: relative;
      margin-bottom: 8px;
      word-break: break-all;
    }

    .setup-copy {
      position: absolute;
      top: 8px;
      right: 8px;
      background: var(--bg-raised);
      border: 1px solid var(--border);
      color: var(--text-muted);
      padding: 3px 10px;
      border-radius: 10px;
      font-size: 10px;
      font-family: var(--font-label);
      cursor: pointer;
      transition: all 0.15s;
    }

    .setup-copy:hover {
      background: var(--border);
      color: var(--text-primary);
    }

    .setup-step {
      display: none;
    }

    .setup-step.active {
      display: block;
    }

    .setup-detected {
      color: var(--accent-green);
      font-size: 12px;
      margin-bottom: 8px;
    }

    /* ── Policy Tester (evaluate a custom policy against text in-browser) ── */
    .tester-row {
      display: flex;
      gap: 10px;
      align-items: stretch;
      flex-wrap: wrap;
      margin-bottom: 10px;
    }

    .tester-select,
    .tester-input {
      background: var(--bg-inset);
      border: 1px solid var(--border);
      color: var(--text-primary);
      padding: 9px 12px;
      border-radius: var(--radius);
      font-family: var(--font-data);
      font-size: 13px;
    }

    .tester-select {
      min-width: 200px;
    }

    .tester-input {
      flex: 1;
      min-width: 220px;
      resize: vertical;
      min-height: 60px;
    }

    .tester-btn {
      background: var(--accent-cyan);
      color: var(--bg-deep);
      border: none;
      padding: 0 22px;
      border-radius: 20px;
      font-family: var(--font-label);
      font-size: 12px;
      font-weight: 700;
      cursor: pointer;
      letter-spacing: 0.3px;
      transition: opacity 0.15s;
    }

    .tester-btn:hover { opacity: 0.9; }
    .tester-btn:disabled { opacity: 0.4; cursor: wait; }

    .tester-result {
      padding: 10px 14px;
      border-radius: var(--radius);
      font-family: var(--font-data);
      font-size: 13px;
      border: 1px solid var(--border-subtle);
      background: var(--bg-inset);
      color: var(--text-muted);
    }

    .tester-result.passed {
      border-color: rgba(46, 213, 115, 0.45);
      color: var(--accent-green);
    }

    .tester-result.violated {
      border-color: rgba(255, 71, 87, 0.45);
      color: var(--accent-red);
    }

    /* ── Profile drawer (slides in when email chip is clicked) ── */
    .profile-drawer {
      position: fixed;
      top: 0;
      right: 0;
      bottom: 0;
      width: 360px;
      max-width: 92vw;
      background: var(--bg-surface);
      border-left: 1px solid var(--border);
      box-shadow: -16px 0 60px rgba(0, 0, 0, 0.4);
      transform: translateX(100%);
      transition: transform 0.18s ease-out;
      z-index: 900;
      display: flex;
      flex-direction: column;
    }

    .profile-drawer.open {
      transform: translateX(0);
    }

    .profile-drawer-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 18px 20px;
      border-bottom: 1px solid var(--border);
    }

    .profile-drawer-title {
      font-family: var(--font-label);
      font-size: 12px;
      letter-spacing: 0.6px;
      color: var(--text-muted);
      text-transform: uppercase;
    }

    .profile-drawer-close {
      background: transparent;
      border: none;
      color: var(--text-muted);
      font-size: 20px;
      cursor: pointer;
      padding: 0 6px;
      line-height: 1;
    }

    .profile-drawer-close:hover {
      color: var(--text-primary);
    }

    .profile-drawer-body {
      flex: 1;
      overflow-y: auto;
      padding: 20px;
    }

    .profile-row {
      display: flex;
      flex-direction: column;
      gap: 4px;
      padding: 10px 0;
      border-bottom: 1px solid var(--border);
    }

    .profile-row:last-of-type {
      border-bottom: none;
    }

    .profile-row-label {
      font-family: var(--font-label);
      font-size: 10px;
      letter-spacing: 0.5px;
      color: var(--text-dim);
      text-transform: uppercase;
    }

    .profile-row-value {
      font-size: 14px;
      color: var(--text-primary);
      word-break: break-all;
    }

    .profile-row-value.muted {
      color: var(--text-muted);
      font-size: 13px;
    }

    .profile-actions {
      display: flex;
      flex-direction: column;
      gap: 8px;
      padding: 16px 20px;
      border-top: 1px solid var(--border);
    }

    .profile-action-btn {
      background: transparent;
      border: 1px solid var(--border);
      color: var(--text-primary);
      padding: 10px 14px;
      border-radius: 6px;
      font-family: var(--font-label);
      font-size: 12px;
      letter-spacing: 0.4px;
      cursor: pointer;
      transition: border-color 0.15s, color 0.15s;
    }

    .profile-action-btn:hover {
      border-color: var(--accent-cyan);
      color: var(--accent-cyan);
    }

    .profile-action-btn.danger:hover {
      border-color: var(--accent-red);
      color: var(--accent-red);
    }

    /* ── Routing topology panel ── */
    .topology {
      display: flex;
      align-items: stretch;
      gap: 0;
      padding: 18px;
      overflow-x: auto;
    }

    .topology-hop {
      flex: 1 1 0;
      min-width: 160px;
      background: var(--bg-elevated, rgba(255, 255, 255, 0.03));
      border: 1px solid var(--border);
      border-radius: 8px;
      padding: 12px 14px;
      display: flex;
      flex-direction: column;
      gap: 6px;
    }

    .topology-hop.scope-local {
      border-color: rgba(0, 212, 255, 0.35);
    }

    .topology-hop.scope-upstream {
      border-color: rgba(0, 212, 170, 0.35);
    }

    .topology-hop.scope-provider {
      border-color: rgba(160, 130, 255, 0.35);
    }

    .topology-arrow {
      flex: 0 0 28px;
      display: flex;
      align-items: center;
      justify-content: center;
      color: var(--text-dim);
      font-size: 18px;
      font-family: var(--font-label);
    }

    .topology-hop-name {
      font-family: var(--font-label);
      font-size: 12px;
      letter-spacing: 0.5px;
      color: var(--text-primary);
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .topology-dot {
      display: inline-block;
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background: var(--accent-green, #50e08c);
    }

    .topology-dot.muted {
      background: var(--text-dim);
    }

    .topology-hop-scope {
      font-family: var(--font-label);
      font-size: 9px;
      letter-spacing: 0.5px;
      color: var(--text-dim);
      text-transform: uppercase;
    }

    .topology-hop-url {
      font-family: var(--font-mono, monospace);
      font-size: 10px;
      color: var(--text-muted);
      word-break: break-all;
      line-height: 1.3;
    }

    .topology-hop-list {
      list-style: none;
      padding: 0;
      margin: 4px 0 0 0;
      display: flex;
      flex-direction: column;
      gap: 3px;
    }

    .topology-hop-list li {
      font-size: 11px;
      color: var(--text-muted);
      padding-left: 12px;
      position: relative;
    }

    .topology-hop-list li::before {
      content: "→";
      position: absolute;
      left: 0;
      color: var(--text-dim);
    }

    .topology-latency {
      font-family: var(--font-mono, monospace);
      font-size: 10px;
      color: var(--accent-cyan);
      margin-top: 2px;
    }

    .topology-latency .muted {
      color: var(--text-dim);
    }

    .topology-mode-bar {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 12px;
      padding: 0 18px 6px 18px;
    }

    .topology-mode-toggle {
      display: inline-flex;
      gap: 0;
      background: var(--bg-elevated, rgba(255, 255, 255, 0.03));
      border: 1px solid var(--border);
      border-radius: 6px;
      overflow: hidden;
    }

    .topology-mode-toggle button {
      background: transparent;
      border: none;
      color: var(--text-muted);
      font-family: var(--font-label);
      font-size: 11px;
      letter-spacing: 0.4px;
      padding: 6px 14px;
      cursor: pointer;
      transition: background 0.15s, color 0.15s;
    }

    .topology-mode-toggle button.active {
      background: var(--accent-cyan);
      color: var(--bg-base, #08111c);
    }

    .topology-mode-toggle button:not(.active):hover {
      color: var(--text-primary);
    }

    .topology-mode-help {
      font-size: 11px;
      color: var(--text-dim);
    }

    @media (max-width: 720px) {
      .topology { flex-direction: column; }
      .topology-arrow { transform: rotate(90deg); flex-basis: auto; }
    }

    /* ── Header settings (open setup wizard after first run) ── */
    .settings-btn {
      background: transparent;
      border: 1px solid var(--border);
      color: var(--text-muted);
      padding: 4px 12px;
      border-radius: 14px;
      font-family: var(--font-label);
      font-size: 11px;
      letter-spacing: 0.4px;
      cursor: pointer;
      margin-left: 10px;
      transition: border-color 0.15s, color 0.15s;
    }

    .settings-btn:hover {
      border-color: var(--accent-cyan);
      color: var(--accent-cyan);
    }
  </style>
</head>

<body>
  <div class="setup-overlay" id="setup-overlay" style="display:none;">
    <div class="setup-card">
      <button class="setup-close" id="setup-close-btn" onclick="closeSetup()" title="Close" style="display:none;" aria-label="Close">&times;</button>
      <div class="setup-step active" id="setup-step-1">
        <h2>Welcome to SafeClaw</h2>
        <p>Configure your LLM provider to get started.</p>
        <div id="setup-detected"></div>
        <div class="setup-options">
          <div class="setup-option" onclick="selectSetupOption('byok', event)">
            <h3>I have API keys</h3>
            <p>OpenAI, Anthropic, or local</p>
          </div>
          <div class="setup-option" onclick="selectSetupOption('tokenrouter', event)">
            <h3>Use TokenRouter</h3>
            <p>Route via palebluedot.ai</p>
          </div>
          <div class="setup-option" onclick="selectSetupOption('aceteam', event)">
            <h3>Use AceTeam</h3>
            <p>$5 free credit, no key needed</p>
          </div>
        </div>
        <div id="setup-byok" style="display:none;">
          <input class="setup-input" id="setup-api-key" type="password" placeholder="sk-... or paste your API key">
          <input class="setup-input" id="setup-base-url" type="text" placeholder="Base URL (optional) — e.g. https://open.palebluedot.ai/v1">
          <button class="setup-btn" onclick="saveApiKey()">Save &amp; Continue</button>
        </div>
        <div id="setup-aceteam" style="display:none;">
          <p style="font-size:13px;margin-bottom:10px;">Sign in to AceTeam — we'll connect SafeClaw to your account. $5 free credit included; top up from your dashboard when it runs out.</p>
          <button class="setup-btn" onclick="connectAceTeam()">Continue to AceTeam</button>
          <p id="aceteam-status" style="font-size:12px;color:var(--text-muted);margin-top:8px;"></p>
        </div>
      </div>
      <div class="setup-step" id="setup-step-2">
        <h2>Point your agent here</h2>
        <p>Add this to your terminal:</p>
        <div class="setup-code" id="setup-export-cmd">
          <button class="setup-copy" onclick="copySetupText('setup-export-cmd')">Copy</button>
          export OPENAI_BASE_URL=http://localhost:8899/v1
        </div>
        <p style="margin-top:16px;">Using Claude Code? Add to your config:</p>
        <div class="setup-code" id="setup-mcp-config">
          <button class="setup-copy" onclick="copySetupText('setup-mcp-config')">Copy</button>
          {"mcpServers":{"aceteam":{"type":"streamable-http","url":"http://localhost:8899/mcp/"}}}
        </div>
        <button class="setup-btn" onclick="closeSetup()" style="margin-top:16px;">Got it, start using SafeClaw</button>
      </div>
    </div>
  </div>
  <h1>
    Agent Safety Net <span class="live pulse">&#x25cf; live</span>
    <div class="safety-toggle">
      <span class="toggle-label" id="safety-label">Safety ON</span>
      <div class="toggle-switch on" id="safety-toggle" onclick="toggleSafety()"></div>
    </div>
    <div class="view-toggle">
      <button id="btn-dev" class="active" onclick="setView('dev')">Developer</button>
      <button id="btn-ciso" onclick="setView('ciso')">Executive</button>
    </div>
    <button class="settings-btn" id="settings-btn" onclick="openProfile()" title="API key &amp; agent setup">
      <span id="settings-btn-label">SETUP</span>
    </button>
  </h1>
  <aside class="profile-drawer" id="profile-drawer" aria-hidden="true">
    <div class="profile-drawer-header">
      <span class="profile-drawer-title">Connected account</span>
      <button class="profile-drawer-close" onclick="closeProfile()" aria-label="Close">&times;</button>
    </div>
    <div class="profile-drawer-body">
      <div class="profile-row">
        <span class="profile-row-label">Email</span>
        <span class="profile-row-value" id="profile-email">—</span>
      </div>
      <div class="profile-row">
        <span class="profile-row-label">Provider</span>
        <span class="profile-row-value" id="profile-provider">—</span>
      </div>
      <div class="profile-row">
        <span class="profile-row-label">Key (hint)</span>
        <span class="profile-row-value" id="profile-key-hint">—</span>
      </div>
      <div class="profile-row">
        <span class="profile-row-label">Organization</span>
        <span class="profile-row-value" id="profile-org">—</span>
      </div>
    </div>
    <div class="profile-actions">
      <button class="profile-action-btn" onclick="openSetup(); closeProfile();">Reconfigure / change provider</button>
      <button class="profile-action-btn danger" onclick="disconnectKey()">Disconnect</button>
    </div>
  </aside>

  <!-- ===== DEVELOPER VIEW ===== -->
  <div id="view-dev">
    <div id="dev-session-info" class="session-info"></div>

    <div class="section">
      <h2>Routing</h2>
      <div class="topology-mode-bar">
        <div class="topology-mode-toggle" role="tablist" aria-label="Routing mode">
          <button id="mode-safe" class="active" onclick="showRoutingMode('safe')">Safe (default)</button>
          <button id="mode-direct" onclick="showRoutingMode('direct')">Direct</button>
        </div>
        <span class="topology-mode-help" id="mode-help">Local proxy enforces your custom policies + DLP before traffic leaves the machine.</span>
      </div>
      <div class="topology" id="routing-topology">
        <span style="color:var(--text-dim);font-size:12px;">Loading routing…</span>
      </div>
    </div>

    <div class="setup-overlay" id="direct-mode-overlay" style="display:none;">
      <div class="setup-card" style="max-width:600px;">
        <button class="setup-close" onclick="closeDirectModeOverlay()" title="Close" aria-label="Close">&times;</button>
        <h2>Switch to Direct mode</h2>
        <p>Direct mode points OpenClaw straight at the AceTeam Gateway, bypassing this local proxy. You lose:</p>
        <ul style="font-size:13px;color:var(--text-muted);margin-bottom:14px;line-height:1.6;">
          <li>Custom policies (the rules in the dashboard's Custom Policies section)</li>
          <li>DLP / secret-leak scanning</li>
          <li>Local audit log + cost dashboard</li>
        </ul>
        <p>You gain ~one network hop of latency.</p>
        <p style="margin-top:14px;">Edit <code>~/safeclaw/config/openclaw.json</code> and replace <code>http://aep-proxy:8899/v1</code> with <code>https://aceteam.ai/api/gateway/v1</code> on both <code>openai</code> and <code>anthropic</code> providers, then restart OpenClaw:</p>
        <div class="setup-code" id="direct-mode-cmd">
          <button class="setup-copy" onclick="copySetupText('direct-mode-cmd')">Copy</button>
          docker compose -f docker-compose.yml -f docker-compose.safe.yml restart openclaw-gateway openclaw-cli
        </div>
        <p style="margin-top:14px;font-size:12px;color:var(--text-dim);">To return to Safe mode, swap the URLs back and restart again.</p>
      </div>
    </div>

    <div class="grid">
      <div class="card">
        <div class="card-label">Total Cost</div>
        <div class="card-value cost" id="cost">$0.000000</div>
      </div>
      <div class="card">
        <div class="card-label">Calls</div>
        <div class="card-value calls" id="calls">0</div>
      </div>
      <div class="card">
        <div class="card-label">Safety Status</div>
        <div class="card-value" id="status"></div>
      </div>
    </div>

    <div class="section" id="budget-section" style="display:none;">
      <h2>Budget</h2>
      <div style="display:flex;gap:16px;align-items:center;">
        <div style="flex:1;">
          <div class="bar-container" style="height:12px;margin:0;">
            <div id="budget-bar"
              style="height:100%;background:var(--accent-cyan);border-radius:4px;transition:width 0.3s;"></div>
          </div>
        </div>
        <div id="budget-text" style="font-size:14px;color:var(--text-muted);white-space:nowrap;"></div>
      </div>
    </div>

    <div class="section" id="policies-section" style="animation-delay: 0.22s;">
      <h2>Policies</h2>
      <div id="policies-container"></div>
    </div>

    <div class="section" id="policy-tester-section" style="animation-delay: 0.24s;">
      <h2>Policy Tester</h2>
      <p style="color:var(--text-muted);font-size:12px;margin:0 0 10px;">
        Evaluate a natural-language policy against arbitrary text, without
        running an agent. Pick one of the rules defined above, paste in a
        sample, and see whether it passes or gets flagged.
      </p>
      <div class="tester-row">
        <select class="tester-select" id="tester-policy"></select>
        <textarea class="tester-input" id="tester-text"
          placeholder="Enter text to evaluate against the selected policy…"></textarea>
        <button class="tester-btn" id="tester-run" onclick="runPolicyTest()">Test</button>
      </div>
      <div class="tester-result" id="tester-result">Pick a policy and enter text, then press Test.</div>
    </div>

    <div class="section">
      <h2>Safety Signals</h2>
      <div class="filter-bar" id="signal-filters"></div>
      <div id="signals"></div>
    </div>

    <div class="section" id="governance-section" style="display:none;">
      <h2>Governance Context</h2>
      <div id="governance"></div>
    </div>

    <div class="section" id="attestation-section" style="display:none;">
      <h2>Attestation (Signed Verdicts)</h2>
      <div id="attestation-details"></div>
    </div>

    <div class="section">
      <h2>Call Timeline</h2>
      <div id="spans"></div>
    </div>
  </div>

  <!-- ===== EXECUTIVE / CISO VIEW ===== -->
  <div id="view-ciso" class="hidden">
    <div id="ciso-session-info" class="session-info"></div>
    <div class="grid grid-4">
      <div class="card">
        <div class="card-label">Policy Compliance</div>
        <div class="card-value green" id="ciso-compliance-pct">100%</div>
        <div class="bar-container">
          <div class="bar-fill bar-green" id="ciso-compliance-bar" style="width:100%"></div>
        </div>
      </div>
      <div class="card">
        <div class="card-label">Threats Blocked</div>
        <div class="card-value" id="ciso-blocked" style="color:var(--accent-red);">0</div>
      </div>
      <div class="card">
        <div class="card-label">Total Agent Spend</div>
        <div class="card-value cost" id="ciso-cost">$0.00</div>
      </div>
      <div class="card">
        <div class="card-label">Total Calls</div>
        <div class="card-value calls" id="ciso-calls">0</div>
      </div>
    </div>

    <div class="section" id="ciso-savings-section" style="display:none;">
      <h2>Cost Savings</h2>
      <div class="metric-row"><span class="metric-label">Calls blocked before reaching LLM</span><span
          class="metric-value good" id="ciso-savings-blocked">0</span></div>
      <div class="metric-row"><span class="metric-label">Estimated cost saved</span><span class="metric-value good"
          id="ciso-savings-amount">$0.00</span></div>
      <div class="metric-row"><span class="metric-label">Average cost per call</span><span class="metric-value"
          id="ciso-savings-avg">$0.00</span></div>
    </div>

    <div class="section">
      <h2>Compliance Status</h2>
      <div id="ciso-compliance-body"></div>
    </div>

    <div class="section">
      <h2>Safety Breakdown</h2>
      <div id="ciso-breakdown">
        <div class="metric-row"><span class="metric-label">Calls passed</span><span class="metric-value good"
            id="ciso-pass">0</span></div>
        <div class="metric-row"><span class="metric-label">Calls flagged</span><span class="metric-value warn"
            id="ciso-flag">0</span></div>
        <div class="metric-row"><span class="metric-label">Calls blocked</span><span class="metric-value bad"
            id="ciso-block">0</span></div>
        <div style="margin-top:12px;">
          <div class="bar-container" style="height:12px;">
            <div id="ciso-bar-pass" class="bar-fill bar-green" style="width:100%;float:left;"></div>
            <div id="ciso-bar-flag" class="bar-fill bar-yellow" style="width:0%;float:left;"></div>
            <div id="ciso-bar-block" class="bar-fill bar-red" style="width:0%;float:left;"></div>
          </div>
        </div>
      </div>
    </div>

    <div class="section" id="ciso-entity-section" style="display:none;">
      <h2>Cost by Entity</h2>
      <div id="ciso-entity-breakdown"></div>
    </div>

    <div class="section" id="ciso-governance-section" style="display:none;">
      <h2>Data Governance</h2>
      <div id="ciso-governance"></div>
    </div>

    <div class="section">
      <h2>Attestation</h2>
      <div id="ciso-attestation">
        <div class="compliance-item">
          <div class="compliance-status"><span class="dot dot-green"></span> <span>Safety verdicts recorded for all
              calls</span></div>
          <div class="metric-row"><span class="metric-label">Every PASS/FLAG/BLOCK decision is logged with timestamp and
              detector ID</span><span class="metric-value good">ACTIVE</span></div>
        </div>
        <div class="compliance-item">
          <div class="compliance-status"><span class="dot dot-yellow"></span> <span>Cryptographic signing
              (Ed25519)</span></div>
          <div class="metric-row"><span class="metric-label">Signed verdict chain for tamper-proof audit
              trail</span><span class="metric-value warn">ROADMAP</span></div>
        </div>
        <div class="compliance-item">
          <div class="compliance-status"><span class="dot dot-yellow"></span> <span>Third-party attestation</span></div>
          <div class="metric-row"><span class="metric-label">Independent verification of safety enforcement
              claims</span><span class="metric-value warn">ROADMAP</span></div>
        </div>
      </div>
    </div>
  </div>

  <div class="footer">
    Agent Safety Net &mdash; aceteam.ai
    <div style="margin-top:6px; font-size:10px; letter-spacing:0.3px; text-transform:none; opacity:0.7;">
      Powered by <a href="https://github.com/aceteam-ai/aceteam-aep" style="color:inherit; text-decoration:underline;">AEP</a>
      &mdash; the Agentic Execution Protocol, an open standard for governing every LLM call an agent makes (cost, PII, policy, signed audit).
    </div>
  </div>

  <script>
    var currentView = 'dev';
    var activeFilter = 'all';
    var lastSignals = [];
    var collapsedGroups = {};
    var safetyEnabled = true;

    // Setup wizard — the proxy tells us whether a BYOK key is already stored.
    // (Previously we relied on localStorage, which left users stranded behind a
    // cookie/localStorage clear whenever they wanted to update a key.)
    // Map a provider tag from /api/api-key to the chip text shown in the header.
    // Keeping the truncated key in the title attribute lets users sanity-check
    // which key is active without exposing the full secret.
    var KEY_CHIP_LABELS = {
      aceteam: 'CONNECTED · AceTeam',
      anthropic: 'KEY · Anthropic',
      openai: 'KEY · OpenAI',
    };
    function refreshKeyHint(info) {
      var btn = document.getElementById('settings-btn');
      var label = document.getElementById('settings-btn-label');
      if (!label) return;
      if (!(info && info.set)) {
        label.textContent = 'SETUP';
        if (btn) btn.title = 'API key & agent setup';
        return;
      }
      // Prefer the verified upstream identity when we have it — that's
      // unambiguous proof of which account this proxy is signed in as.
      var account = info.connected_account;
      if (account && account.email) {
        label.textContent = account.email;
        if (btn) btn.title = 'Connected as ' + account.email + ' (key ' + info.hint + ') — click to change';
        return;
      }
      var friendly = KEY_CHIP_LABELS[info.provider];
      label.textContent = friendly || ('KEY: ' + info.hint);
      if (btn) btn.title = 'Active key: ' + info.hint + ' — click to change';
    }

    function escapeHtml(s) {
      return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
        return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
      });
    }

    // Build the OpenClaw → aep-proxy → (gateway) → provider visual.
    // The OpenClaw client hop isn't returned by the API (the proxy doesn't
    // know the client) so we synthesize it client-side as a constant.
    function renderRouting(data) {
      var container = document.getElementById('routing-topology');
      if (!container) return;
      var hops = (data && data.hops) || [];
      var clientHop = {
        name: 'openclaw',
        label: data && data.client ? data.client : 'Client',
        scope: 'client',
        url: 'http://localhost:18789',
        bullets: ['agent runtime + chat UI'],
      };
      var allHops = [clientHop].concat(hops.map(function (h) {
        var bullets = [];
        if (h.scope === 'local') {
          if (h.safety_enabled === false) {
            bullets.push('safety OFF');
          } else {
            (h.detectors || []).forEach(function (d) {
              if (d) bullets.push(d.replace(/_/g, ' '));
            });
            if (h.custom_policies_enabled > 0) {
              bullets.push(h.custom_policies_enabled + ' custom policy / policies');
            }
          }
        } else if (h.scope === 'upstream') {
          (h.enforces || []).forEach(function (e) { bullets.push(e); });
        } else if (h.scope === 'provider') {
          bullets.push('LLM serving');
        }
        return {
          name: h.name,
          label: h.label || h.name,
          scope: h.scope,
          url: h.url || '',
          bullets: bullets,
          latency: h.latency || null,
        };
      }));

      var html = '';
      allHops.forEach(function (hop, i) {
        if (i > 0) {
          html += '<div class="topology-arrow">→</div>';
        }
        html += '<div class="topology-hop scope-' + escapeHtml(hop.scope) + '">';
        html += '<div class="topology-hop-name"><span class="topology-dot"></span>' + escapeHtml(hop.label) + '</div>';
        html += '<div class="topology-hop-scope">' + escapeHtml(hop.scope) + '</div>';
        if (hop.url) {
          html += '<div class="topology-hop-url">' + escapeHtml(hop.url) + '</div>';
        }
        if (hop.bullets.length > 0) {
          html += '<ul class="topology-hop-list">';
          hop.bullets.forEach(function (b) {
            html += '<li>' + escapeHtml(b) + '</li>';
          });
          html += '</ul>';
        }
        if (hop.latency && hop.latency.count > 0) {
          html += '<div class="topology-latency">';
          html += escapeHtml(hop.latency.avg_ms) + 'ms <span class="muted">avg</span>';
          if (hop.latency.count > 1) {
            html += ' ± ' + escapeHtml(hop.latency.stddev_ms) + 'ms';
          }
          html += ' <span class="muted">(' + escapeHtml(hop.latency.count) + ' calls)</span>';
          html += '</div>';
        }
        html += '</div>';
      });
      container.innerHTML = html;
    }

    // Direct-mode toggle: clicking "Direct" opens the manual-instructions
    // modal (we can't rewrite openclaw.json from the proxy without a new
    // bind mount). Until the user actually edits the config, the UI stays
    // on "Safe" — flipping the visual state would mislead them about the
    // running config. We'll auto-detect mode in a future iteration when
    // the proxy can read openclaw.json directly.
    function showRoutingMode(mode) {
      if (mode === 'direct') {
        document.getElementById('direct-mode-overlay').style.display = 'flex';
        return;
      }
      document.getElementById('mode-safe').classList.add('active');
      document.getElementById('mode-direct').classList.remove('active');
    }
    function closeDirectModeOverlay() {
      document.getElementById('direct-mode-overlay').style.display = 'none';
      // Snap visual back to Safe — the user only cancelled out of the
      // explainer, they didn't actually switch modes.
      showRoutingMode('safe');
    }

    function refreshRouting() {
      fetch('api/routing').then(function (r) {
        if (!r.ok) throw new Error('HTTP ' + r.status);
        return r.json();
      }).then(renderRouting).catch(function () {
        var container = document.getElementById('routing-topology');
        if (container) container.innerHTML = '<span style="color:var(--text-dim);font-size:12px;">Routing unavailable.</span>';
      });
    }

    function checkFirstRun() {
      fetch('api/api-key').then(function (r) {
        if (!r.ok) throw new Error('HTTP ' + r.status);
        return r.json();
      }).then(function (info) {
        refreshKeyHint(info);
        if (!info.set) {
          document.getElementById('setup-overlay').style.display = 'flex';
        }
      }).catch(function () { });
    }

    // Profile drawer — opens on email-chip click for an already-connected user.
    // First-run (no key set) falls back to the setup overlay so the user has a
    // way to provision a key. Reconfigure / disconnect actions live in the
    // drawer; the overlay is reserved for initial provisioning.
    function openProfile() {
      fetch('api/api-key').then(function (r) { return r.json(); }).then(function (info) {
        if (!info || !info.set) {
          openSetup();
          return;
        }
        var account = info.connected_account || {};
        document.getElementById('profile-email').textContent = account.email || '(no email — direct BYOK)';
        // KEY_CHIP_LABELS look like "CONNECTED · AceTeam" / "KEY · OpenAI". Pop
        // the part after the · separator for a clean provider name; fall back
        // to the raw provider id (or "Unknown") when no friendly label exists.
        var rawLabel = KEY_CHIP_LABELS[info.provider];
        var providerName = rawLabel ? rawLabel.split('·').pop().trim() : (info.provider || 'Unknown');
        document.getElementById('profile-provider').textContent = providerName;
        document.getElementById('profile-key-hint').textContent = info.hint || '—';
        // Prefer the human-readable org name; fall back to the UUID only when
        // we couldn't look up a name (older keys, BYOK, transient lookup miss).
        document.getElementById('profile-org').textContent = account.organization_name || account.organization_id || '—';
        var drawer = document.getElementById('profile-drawer');
        drawer.classList.add('open');
        drawer.setAttribute('aria-hidden', 'false');
      }).catch(function () {
        openSetup();
      });
    }

    function closeProfile() {
      var drawer = document.getElementById('profile-drawer');
      drawer.classList.remove('open');
      drawer.setAttribute('aria-hidden', 'true');
    }

    function disconnectKey() {
      if (!confirm('Disconnect from AceTeam? You will need to sign in again to use SafeClaw.')) return;
      fetch('api/api-key', { method: 'DELETE' })
        .then(function (r) { return r.json(); })
        .then(function (info) {
          refreshKeyHint(info);
          closeProfile();
          // Surface the setup overlay so user can reconnect (or pick a different provider).
          var overlay = document.getElementById('setup-overlay');
          var closeBtn = document.getElementById('setup-close-btn');
          if (closeBtn) closeBtn.style.display = 'block';
          overlay.style.display = 'flex';
        }).catch(function () {
          alert('Failed to disconnect. Is the proxy running?');
        });
    }

    function openSetup() {
      document.getElementById('setup-step-1').classList.add('active');
      document.getElementById('setup-step-2').classList.remove('active');
      document.getElementById('setup-byok').style.display = 'none';
      document.getElementById('setup-aceteam').style.display = 'none';
      document.getElementById('setup-api-key').value = '';
      document.getElementById('setup-base-url').value = '';
      var options = document.querySelectorAll('.setup-option');
      for (var i = 0; i < options.length; i++) { options[i].classList.remove('selected'); }
      // Manual open (vs forced first-run) → user already has a way out.
      var closeBtn = document.getElementById('setup-close-btn');
      if (closeBtn) closeBtn.style.display = 'block';
      document.getElementById('setup-overlay').style.display = 'flex';
    }

    function selectSetupOption(option, evt) {
      var options = document.querySelectorAll('.setup-option');
      for (var i = 0; i < options.length; i++) { options[i].classList.remove('selected'); }
      if (evt && evt.currentTarget) { evt.currentTarget.classList.add('selected'); }
      var showByok = option === 'byok' || option === 'tokenrouter';
      document.getElementById('setup-byok').style.display = showByok ? 'block' : 'none';
      document.getElementById('setup-aceteam').style.display = option === 'aceteam' ? 'block' : 'none';
      var baseUrl = document.getElementById('setup-base-url');
      var apiKey = document.getElementById('setup-api-key');
      if (option === 'tokenrouter') {
        baseUrl.value = 'https://open.palebluedot.ai/v1';
        apiKey.placeholder = 'TokenRouter API key';
      } else if (option === 'byok') {
        baseUrl.value = '';
        apiKey.placeholder = 'sk-... or paste your API key';
      }
    }

    function saveApiKey() {
      var input = document.getElementById('setup-api-key');
      var baseUrlInput = document.getElementById('setup-base-url');
      var btn = input.parentElement.querySelector('.setup-btn');
      var key = input.value.trim();
      var baseUrl = baseUrlInput.value.trim();
      if (!key) return;
      if (btn) { btn.disabled = true; btn.textContent = 'Saving…'; }
      var payload = { api_key: key };
      if (baseUrl) payload.base_url = baseUrl;
      fetch('api/api-key', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
      }).then(function (r) {
        if (!r.ok) throw new Error('HTTP ' + r.status);
        return r.json();
      }).then(function (info) {
        refreshKeyHint(info);
        input.value = '';
        baseUrlInput.value = '';
        document.getElementById('setup-step-1').classList.remove('active');
        document.getElementById('setup-step-2').classList.add('active');
      }).catch(function () {
        alert('Failed to save API key. Is the proxy running?');
      }).then(function () {
        if (btn) { btn.disabled = false; btn.textContent = 'Save & Continue'; }
      });
    }

    function copySetupText(elementId) {
      var el = document.getElementById(elementId);
      var copyBtn = el.querySelector('.setup-copy');
      var fullText = el.textContent || '';
      var btnText = copyBtn ? (copyBtn.textContent || '') : '';
      var text = fullText.replace(btnText, '').trim();
      navigator.clipboard.writeText(text);
    }

    function closeSetup() {
      document.getElementById('setup-overlay').style.display = 'none';
    }

    // "Use AceTeam" option: bounce the user to aceteam.ai, where they sign in,
    // we mint a key, and they're redirected back here with the key in the URL
    // fragment (handled by tryConsumeAceteamReturn on next load).
    var ACETEAM_CONNECT_URL = 'https://aceteam.ai/connect/safeclaw';
    function connectAceTeam() {
      var callback = window.location.origin + '/dashboard/';
      var status = document.getElementById('aceteam-status');
      if (status) status.textContent = 'Opening aceteam.ai…';
      window.location.href = ACETEAM_CONNECT_URL + '?callback=' + encodeURIComponent(callback);
    }

    // Handle the redirect back from aceteam.ai/connect/safeclaw. The bridge
    // page appends `#aceteam_key=...&aceteam_base=...` to our dashboard URL;
    // we POST those to the same /api/api-key endpoint BYOK uses, then clear
    // the fragment so a refresh doesn't re-apply a stale key.
    function tryConsumeAceteamReturn() {
      var hash = window.location.hash || '';
      if (hash.length < 2) return false;
      var params = new URLSearchParams(hash.slice(1));
      var key = params.get('aceteam_key');
      var baseUrl = params.get('aceteam_base');
      var email = params.get('aceteam_email');
      var orgId = params.get('aceteam_org_id');
      var orgName = params.get('aceteam_org_name');
      if (!key) return false;
      history.replaceState(null, '', window.location.pathname);
      var body = { api_key: key, base_url: baseUrl || undefined };
      if (email || orgId || orgName) {
        body.connected_account = {};
        if (email) body.connected_account.email = email;
        if (orgId) body.connected_account.organization_id = orgId;
        if (orgName) body.connected_account.organization_name = orgName;
      }
      fetch('api/api-key', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body)
      }).then(function (r) {
        if (!r.ok) throw new Error('HTTP ' + r.status);
        return r.json();
      }).then(function (info) {
        refreshKeyHint(info);
        // Skip the overlay entirely — user came back already-provisioned.
        var overlay = document.getElementById('setup-overlay');
        if (overlay) overlay.style.display = 'none';
      }).catch(function () {
        alert('Connected to AceTeam, but the proxy refused the key. Try again from the dashboard.');
      });
      return true;
    }

    // Run on page load. If we just came back from aceteam.ai we short-circuit
    // the normal "do you have a key?" check since we just saved one.
    if (!tryConsumeAceteamReturn()) {
      checkFirstRun();
    }

    // Routing topology is a low-churn view (target_base_url + detector list);
    // initial fetch + a slow poll picks up dashboard-driven changes (BYOK
    // base URL edits, safety toggle) without flooding the endpoint.
    refreshRouting();
    setInterval(refreshRouting, 30000);

    function toggleSafety() {
      safetyEnabled = !safetyEnabled;
      fetch('api/safety', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ enabled: safetyEnabled })
      });
      var toggle = document.getElementById('safety-toggle');
      var label = document.getElementById('safety-label');
      toggle.className = 'toggle-switch' + (safetyEnabled ? ' on' : '');
      label.textContent = safetyEnabled ? 'Safety ON' : 'Safety OFF';
    }

    function submitVerdict(signalType, score, verdict) {
      fetch('api/feedback', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ signal_type: signalType, score: score, verdict: verdict })
      });
    }

    function setView(view) {
      currentView = view;
      document.getElementById('view-dev').className = view === 'dev' ? '' : 'hidden';
      document.getElementById('view-ciso').className = view === 'ciso' ? '' : 'hidden';
      document.getElementById('btn-dev').className = view === 'dev' ? 'active' : '';
      document.getElementById('btn-ciso').className = view === 'ciso' ? 'active' : '';
    }

    function esc(s) {
      var d = document.createElement('div');
      d.textContent = s;
      return d.textContent;
    }

    function setBadge(el, action) {
      el.textContent = '';
      var span = document.createElement('span');
      var validActions = ['pass', 'flag', 'block'];
      var cls = validActions.indexOf(action) >= 0 ? action : 'pass';
      span.className = 'badge badge-' + cls;
      span.textContent = action.toUpperCase();
      el.appendChild(span);
    }

    function makeSmallBadge(action) {
      var span = document.createElement('span');
      var validActions = ['pass', 'flag', 'block'];
      var cls = validActions.indexOf(action) >= 0 ? action : 'pass';
      span.className = 'badge badge-sm badge-' + cls;
      span.textContent = action.toUpperCase();
      return span;
    }

    // Determine per-call enforcement from signals
    function getCallAction(callId, signals) {
      var callSignals = signals.filter(function (s) { return s.call_id === callId; });
      if (callSignals.length === 0) return 'pass';
      var hasHigh = callSignals.some(function (s) { return s.severity === 'high'; });
      if (hasHigh) return 'block';
      var hasMedium = callSignals.some(function (s) { return s.severity === 'medium'; });
      if (hasMedium) return 'flag';
      return 'pass';
    }

    function renderFilterBar(container, signals) {
      container.textContent = '';
      var types = {};
      signals.forEach(function (s) { types[s.type] = (types[s.type] || 0) + 1; });
      var typeKeys = Object.keys(types);
      if (typeKeys.length <= 1) return; // No filter needed for 0 or 1 type

      var allBtn = document.createElement('button');
      allBtn.className = 'filter-btn' + (activeFilter === 'all' ? ' active' : '');
      allBtn.textContent = 'ALL (' + signals.length + ')';
      allBtn.onclick = function () { activeFilter = 'all'; renderSignals(document.getElementById('signals'), lastSignals); renderFilterBar(container, lastSignals); };
      container.appendChild(allBtn);

      typeKeys.forEach(function (t) {
        var btn = document.createElement('button');
        btn.className = 'filter-btn' + (activeFilter === t ? ' active' : '');
        btn.textContent = t.toUpperCase() + ' (' + types[t] + ')';
        btn.onclick = function () { activeFilter = t; renderSignals(document.getElementById('signals'), lastSignals); renderFilterBar(container, lastSignals); };
        container.appendChild(btn);
      });
    }

    function renderSignals(container, signals) {
      container.textContent = '';
      // Apply filter
      var filtered = activeFilter === 'all' ? signals : signals.filter(function (s) { return s.type === activeFilter; });
      if (filtered.length === 0) {
        var empty = document.createElement('div');
        empty.className = 'empty';
        empty.textContent = signals.length === 0 ? 'No signals detected' : 'No ' + activeFilter + ' signals';
        container.appendChild(empty);
        return;
      }

      // Group by call_id
      var groups = {};
      var groupOrder = [];
      filtered.forEach(function (s) {
        var key = s.call_id || 'unknown';
        if (!groups[key]) { groups[key] = []; groupOrder.push(key); }
        groups[key].push(s);
      });

      // If only one group, render flat
      if (groupOrder.length === 1) {
        renderSignalList(container, filtered);
        return;
      }

      groupOrder.forEach(function (callId) {
        var group = groups[callId];
        var div = document.createElement('div');
        div.className = 'call-group';

        var isCollapsed = collapsedGroups[callId] === true;
        var header = document.createElement('div');
        header.className = 'call-group-header';
        var arrow = document.createElement('span');
        arrow.className = 'call-group-arrow' + (isCollapsed ? ' collapsed' : '');
        arrow.textContent = '\u25BC';
        header.appendChild(arrow);
        var label = document.createElement('span');
        label.textContent = 'Call ' + callId + ': ' + group.length + ' signal' + (group.length > 1 ? 's' : '');
        header.appendChild(label);
        header.onclick = function () {
          collapsedGroups[callId] = !collapsedGroups[callId];
          renderSignals(container, signals);
        };
        div.appendChild(header);

        if (!isCollapsed) {
          var body = document.createElement('div');
          body.className = 'call-group-body';
          renderSignalList(body, group);
          div.appendChild(body);
        }

        container.appendChild(div);
      });
    }

    function renderSignalList(container, signals) {
      signals.forEach(function (s) {
        var row = document.createElement('div');
        var validSev = ['high', 'medium', 'low'];
        var sevCls = validSev.indexOf(s.severity) >= 0 ? s.severity : 'low';
        row.className = 'signal signal-' + sevCls;

        var sev = document.createElement('span');
        sev.className = 'sev sev-' + sevCls;
        sev.textContent = s.severity.toUpperCase();
        row.appendChild(sev);

        var detail = document.createElement('span');
        detail.textContent = esc(s.type) + ': ' + esc(s.detail);
        row.appendChild(detail);

        // Confidence percentage for trust_engine signals, score badge for others
        if (s.score !== null && s.score !== undefined) {
          if (s.type === 'trust_engine' && s.score <= 1) {
            var confPct = document.createElement('span');
            var pct = Math.round(s.score * 100);
            confPct.className = 'confidence-pct' + (pct >= 70 ? ' confidence-high' : pct >= 40 ? ' confidence-med' : ' confidence-low');
            confPct.textContent = pct + '% unsafe';
            row.appendChild(confPct);
          } else {
            var scoreBadge = document.createElement('span');
            scoreBadge.className = 'score-badge';
            scoreBadge.textContent = '(' + (s.score <= 1 ? s.score.toFixed(2) : s.score.toFixed(1) + 'x') + ')';
            row.appendChild(scoreBadge);
          }
        }

        // Review buttons for flagged signals (medium severity)
        if (s.severity === 'medium') {
          var btns = document.createElement('span');
          btns.className = 'review-btns';
          var confirmBtn = document.createElement('button');
          confirmBtn.className = 'review-btn review-btn-confirm';
          confirmBtn.textContent = 'Confirm';
          confirmBtn.onclick = function () { submitVerdict(s.type, s.score, 'confirmed'); btns.textContent = 'Confirmed'; };
          btns.appendChild(confirmBtn);
          var dismissBtn = document.createElement('button');
          dismissBtn.className = 'review-btn review-btn-dismiss';
          dismissBtn.textContent = 'Dismiss';
          dismissBtn.onclick = function () { submitVerdict(s.type, s.score, 'dismissed'); btns.textContent = 'Dismissed'; };
          btns.appendChild(dismissBtn);
          row.appendChild(btns);
        }

        container.appendChild(row);
      });
    }

    function renderGovernance(container, govList) {
      var section = document.getElementById('governance-section');
      if (!govList || govList.length === 0) { section.style.display = 'none'; return; }
      section.style.display = '';
      container.textContent = '';
      govList.forEach(function (gov) {
        var callLabel = document.createElement('div');
        callLabel.style.cssText = 'font-size:11px;color:#6bc5e8;margin-top:8px;margin-bottom:4px;';
        callLabel.textContent = 'Call ' + (gov.call_id || '?');
        container.appendChild(callLabel);
        var fields = [
          ['Entity', gov.entity],
          ['Classification', gov.classification],
          ['Consent', gov.consent ? Object.keys(gov.consent).map(function (k) { return k + '=' + gov.consent[k]; }).join(', ') : null],
          ['Budget', gov.budget],
          ['Sources', gov.sources && gov.sources.length ? gov.sources.join(', ') : null],
          ['Trace ID', gov.trace_id]
        ];
        fields.forEach(function (f) {
          if (!f[1]) return;
          var row = document.createElement('div');
          row.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:8px 14px;background:var(--bg-inset);border-radius:6px;margin-bottom:3px;font-size:13px;';
          var label = document.createElement('span');
          label.className = 'span-model';
          label.textContent = f[0];
          row.appendChild(label);
          var val = document.createElement('span');
          val.className = 'span-dur';
          val.textContent = String(f[1]);
          row.appendChild(val);
          container.appendChild(row);
        });
      });
    }

    function renderSpans(container, spans, signals) {
      container.textContent = '';
      if (spans.length === 0) {
        var empty = document.createElement('div');
        empty.className = 'empty';
        empty.textContent = 'No calls recorded';
        container.appendChild(empty);
        return;
      }
      var table = document.createElement('table');
      table.className = 'span-table';
      spans.forEach(function (s) {
        var tr = document.createElement('tr');

        var tdTime = document.createElement('td');
        tdTime.className = 'span-time';
        if (s.started_at) {
          var d = new Date(s.started_at);
          tdTime.textContent = d.toLocaleDateString([], { month: 'short', day: 'numeric' }) + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
        }
        tr.appendChild(tdTime);

        var tdModel = document.createElement('td');
        tdModel.className = 'span-model';
        tdModel.textContent = esc(s.executor);
        tr.appendChild(tdModel);

        var tdStatus = document.createElement('td');
        tdStatus.className = 'span-status';
        var callAction = getCallAction(s.call_id, signals);
        tdStatus.appendChild(makeSmallBadge(callAction));
        tr.appendChild(tdStatus);

        var tdCost = document.createElement('td');
        tdCost.className = 'span-cost';
        if (s.cost > 0) {
          tdCost.textContent = '$' + s.cost.toFixed(6);
        }
        tr.appendChild(tdCost);

        var tdDur = document.createElement('td');
        tdDur.className = 'span-dur';
        tdDur.textContent = s.duration_ms ? Math.round(s.duration_ms) + 'ms' : '...';
        tr.appendChild(tdDur);

        table.appendChild(tr);
      });
      container.appendChild(table);
    }

    function formatDuration(ms) {
      if (ms < 60000) return Math.round(ms / 1000) + ' sec';
      if (ms < 3600000) return Math.round(ms / 60000) + ' min';
      var h = Math.floor(ms / 3600000);
      var m = Math.round((ms % 3600000) / 60000);
      return h + 'h ' + m + 'm';
    }

    function updateSessionInfo(data) {
      var devEl = document.getElementById('dev-session-info');
      var cisoEl = document.getElementById('ciso-session-info');
      var text = '';
      if (data.session_started) {
        var started = new Date(data.session_started);
        var elapsed = Date.now() - started.getTime();
        var startTime = started.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
        text = 'Session started: ' + startTime + ' (' + formatDuration(elapsed) + ')';
      }
      if (devEl) devEl.textContent = text;
      if (cisoEl) cisoEl.textContent = text;
    }

    function updateCisoView(data) {
      // Aggregate signal counts by type
      var piiCount = 0, threatCount = 0, toxicCount = 0, anomalyCount = 0;
      (data.signals || []).forEach(function (s) {
        if (s.type === 'pii') piiCount++;
        else if (s.type === 'agent_threat') threatCount++;
        else if (s.type === 'content_safety') toxicCount++;
        else if (s.type === 'cost_anomaly') anomalyCount++;
      });

      // Count unique CALLS (not signals) that were blocked/flagged
      var blockedCalls = {};
      var flaggedCalls = {};
      (data.signals || []).forEach(function (s) {
        var cid = s.call_id || 'unknown';
        if (s.severity === 'high') blockedCalls[cid] = true;
        else if (s.severity === 'medium') flaggedCalls[cid] = true;
      });
      // A call with both high+medium counts as blocked, not both
      Object.keys(blockedCalls).forEach(function (k) { delete flaggedCalls[k]; });
      var blockCount = Object.keys(blockedCalls).length;
      var flagCount = Object.keys(flaggedCalls).length;

      var totalCalls = data.calls || 0;
      var passCount = Math.max(0, totalCalls - blockCount - flagCount);

      // Policy compliance percentage
      var compliancePct = totalCalls > 0 ? ((totalCalls - blockCount) / totalCalls * 100) : 100;
      var complianceEl = document.getElementById('ciso-compliance-pct');
      complianceEl.textContent = compliancePct.toFixed(1) + '%';
      var complianceBar = document.getElementById('ciso-compliance-bar');
      complianceBar.style.width = compliancePct + '%';
      if (compliancePct > 99) {
        complianceEl.className = 'card-value green';
        complianceBar.className = 'bar-fill bar-green';
      } else if (compliancePct > 95) {
        complianceEl.className = 'card-value';
        complianceEl.style.color = 'var(--accent-amber)';
        complianceBar.className = 'bar-fill bar-yellow';
      } else {
        complianceEl.className = 'card-value';
        complianceEl.style.color = 'var(--accent-red)';
        complianceBar.className = 'bar-fill bar-red';
      }

      document.getElementById('ciso-blocked').textContent = String(blockCount);
      document.getElementById('ciso-cost').textContent = '$' + data.cost.toFixed(2);
      document.getElementById('ciso-calls').textContent = String(totalCalls);

      // Cost savings section
      var savings = data.savings;
      var savingsSection = document.getElementById('ciso-savings-section');
      if (savings && savings.blocked_calls > 0) {
        savingsSection.style.display = '';
        document.getElementById('ciso-savings-blocked').textContent = String(savings.blocked_calls);
        document.getElementById('ciso-savings-amount').textContent = '$' + savings.estimated_savings_usd.toFixed(4);
        document.getElementById('ciso-savings-avg').textContent = '$' + savings.avg_call_cost_usd.toFixed(4);
      } else {
        savingsSection.style.display = 'none';
      }

      // Compliance status - zero-state vs incidents
      var complianceBody = document.getElementById('ciso-compliance-body');
      complianceBody.textContent = '';
      var totalIncidents = piiCount + threatCount + toxicCount + anomalyCount;
      if (totalIncidents === 0 && totalCalls > 0) {
        // Zero-state: all clean
        var zs = document.createElement('div');
        zs.className = 'zero-state';
        zs.innerHTML = '<div class="zero-state-icon" style="color:var(--accent-green);">&#x2714;</div>'
          + '<div class="zero-state-text">All agents compliant</div>'
          + '<div class="zero-state-sub">No safety incidents this session. ' + totalCalls + ' call' + (totalCalls > 1 ? 's' : '') + ' passed.</div>';
        complianceBody.appendChild(zs);
      } else if (totalCalls === 0) {
        var zs2 = document.createElement('div');
        zs2.className = 'zero-state';
        zs2.innerHTML = '<div class="zero-state-icon" style="color:var(--text-dim);">&#x2022;</div>'
          + '<div class="zero-state-text" style="color:var(--text-muted);">Waiting for agent activity</div>'
          + '<div class="zero-state-sub">No calls recorded yet.</div>';
        complianceBody.appendChild(zs2);
      } else {
        // Show incident breakdown
        var items = [
          { dotId: 'pii', label: 'PII Exfiltration', sublabel: 'Incidents this session', count: piiCount, verb: 'blocked' },
          { dotId: 'threat', label: 'Agent Threat Attempts', sublabel: 'Port scans, shell access, credential theft', count: threatCount, verb: 'blocked' },
          { dotId: 'toxic', label: 'Unsafe Content', sublabel: 'Toxic or harmful output', count: toxicCount, verb: 'detected' },
          { dotId: 'cost', label: 'Cost Anomalies', sublabel: 'Spend spikes above baseline', count: anomalyCount, verb: 'flagged' }
        ];
        items.forEach(function (item) {
          var ci = document.createElement('div');
          ci.className = 'compliance-item';
          var dotCls = item.count > 0 ? 'dot dot-red' : 'dot dot-green';
          var valCls = item.count > 0 ? 'metric-value bad' : 'metric-value good';
          ci.innerHTML = '<div class="compliance-status"><span class="' + dotCls + '"></span> <span>' + esc(item.label) + '</span></div>'
            + '<div class="metric-row"><span class="metric-label">' + esc(item.sublabel) + '</span><span class="' + valCls + '">' + item.count + ' ' + item.verb + '</span></div>';
          complianceBody.appendChild(ci);
        });
      }

      // Safety breakdown
      document.getElementById('ciso-pass').textContent = String(passCount);
      document.getElementById('ciso-flag').textContent = String(flagCount);
      document.getElementById('ciso-block').textContent = String(blockCount);

      if (totalCalls > 0) {
        var pPct = (passCount / totalCalls * 100);
        var fPct = (flagCount / totalCalls * 100);
        var bPct = (blockCount / totalCalls * 100);
        document.getElementById('ciso-bar-pass').style.width = pPct + '%';
        document.getElementById('ciso-bar-flag').style.width = fPct + '%';
        document.getElementById('ciso-bar-block').style.width = bPct + '%';
      }

      // Cost per entity breakdown
      var entitySection = document.getElementById('ciso-entity-section');
      var entityContainer = document.getElementById('ciso-entity-breakdown');
      var govList = data.governance || [];
      var spans = data.spans || [];
      if (govList.length > 0) {
        // Build call_id → cost lookup from spans
        var callCost = {};
        spans.forEach(function (s) {
          if (s.call_id) callCost[s.call_id] = (callCost[s.call_id] || 0) + (s.cost || 0);
        });
        // Build entity → { cost, calls } from governance
        var entities = {};
        govList.forEach(function (g) {
          var e = g.entity || 'unknown';
          if (!entities[e]) entities[e] = { cost: 0, calls: 0 };
          entities[e].calls++;
          entities[e].cost += callCost[g.call_id] || 0;
        });
        var entityKeys = Object.keys(entities);
        if (entityKeys.length > 0) {
          entitySection.style.display = '';
          entityContainer.textContent = '';
          entityKeys.forEach(function (e) {
            var row = document.createElement('div');
            row.className = 'entity-row';
            var left = document.createElement('div');
            var nameEl = document.createElement('span');
            nameEl.className = 'entity-name';
            nameEl.textContent = e;
            left.appendChild(nameEl);
            var detailEl = document.createElement('span');
            detailEl.className = 'entity-detail';
            detailEl.textContent = ' (' + entities[e].calls + ' call' + (entities[e].calls > 1 ? 's' : '') + ')';
            left.appendChild(detailEl);
            row.appendChild(left);
            var costEl = document.createElement('span');
            costEl.className = 'entity-cost';
            costEl.textContent = '$' + entities[e].cost.toFixed(4);
            row.appendChild(costEl);
            entityContainer.appendChild(row);
          });
        } else {
          entitySection.style.display = 'none';
        }
      } else {
        entitySection.style.display = 'none';
      }

      // Governance (CISO view)
      var cisoGovSection = document.getElementById('ciso-governance-section');
      if (govList.length > 0) {
        cisoGovSection.style.display = '';
        var gContainer = document.getElementById('ciso-governance');
        gContainer.textContent = '';
        var classifications = {};
        govList.forEach(function (g) {
          if (g.classification) classifications[g.classification] = (classifications[g.classification] || 0) + 1;
        });
        Object.keys(classifications).forEach(function (c) {
          var row = document.createElement('div');
          row.className = 'metric-row';
          var label = document.createElement('span');
          label.className = 'metric-label';
          label.textContent = 'Classification: ' + c;
          row.appendChild(label);
          var val = document.createElement('span');
          val.className = 'metric-value';
          val.textContent = classifications[c] + ' calls';
          row.appendChild(val);
          gContainer.appendChild(row);
        });
      } else {
        cisoGovSection.style.display = 'none';
      }
    }

    async function refresh() {
      try {
        var res = await fetch('api/state');
        var data = await res.json();
        lastSignals = data.signals || [];
        // Sync safety toggle state
        if (data.safety_enabled !== undefined && data.safety_enabled !== safetyEnabled) {
          safetyEnabled = data.safety_enabled;
          document.getElementById('safety-toggle').className = 'toggle-switch' + (safetyEnabled ? ' on' : '');
          document.getElementById('safety-label').textContent = safetyEnabled ? 'Safety ON' : 'Safety OFF';
        }
        // Developer view
        document.getElementById('cost').textContent = '$' + data.cost.toFixed(6);
        document.getElementById('calls').textContent = String(data.calls);
        setBadge(document.getElementById('status'), data.action);
        renderFilterBar(document.getElementById('signal-filters'), lastSignals);
        renderSignals(document.getElementById('signals'), lastSignals);
        renderGovernance(document.getElementById('governance'), data.governance);
        renderAttestation(data.attestation);
        renderBudget(data.budget);
        renderSpans(document.getElementById('spans'), data.spans, lastSignals);
        updateSessionInfo(data);
        // Executive view
        updateCisoView(data);
      } catch (e) { }
    }

    function renderBudget(budget) {
      var section = document.getElementById('budget-section');
      if (!budget) { section.style.display = 'none'; return; }
      section.style.display = '';
      var limit = budget.total || budget.per_session || 0;
      var spent = budget.spent || 0;
      var remaining = budget.remaining != null ? budget.remaining : limit - spent;
      var pct = limit > 0 ? Math.min(100, (spent / limit) * 100) : 0;
      var bar = document.getElementById('budget-bar');
      bar.style.width = pct + '%';
      bar.style.background = pct > 90 ? 'var(--accent-red)' : pct > 70 ? 'var(--accent-amber)' : 'var(--accent-cyan)';
      var label = '$' + spent.toFixed(4) + ' / $' + limit.toFixed(2);
      if (remaining != null) label += ' ($' + remaining.toFixed(4) + ' remaining)';
      document.getElementById('budget-text').textContent = label;
    }

    function renderAttestation(attest) {
      var section = document.getElementById('attestation-section');
      var details = document.getElementById('attestation-details');
      if (!attest || !attest.enabled) {
        section.style.display = 'none';
        return;
      }
      section.style.display = '';

      var rows = [
        ['Signing', 'Ed25519'],
        ['Signer ID', attest.signer_id],
        ['Chain Height', String(attest.chain_height)],
        ['Chain Integrity', 'VERIFIED'],
      ];
      if (attest.latest_hash) {
        rows.push(['Latest Hash', attest.latest_hash.substring(0, 24) + '...']);
      }

      var html = '';
      for (var i = 0; i < rows.length; i++) {
        var label = rows[i][0];
        var value = rows[i][1];
        if (!value) continue;
        var valueClass = (label === 'Chain Integrity') ? 'style="color:var(--accent-green);font-weight:bold;"' : '';
        html += '<div class="entity-row">';
        html += '<span class="entity-name">' + label + '</span>';
        html += '<span class="entity-detail" ' + valueClass + '>' + value + '</span>';
        html += '</div>';
      }
      details.innerHTML = html;
    }
    // ── Policies (proxy API: /dashboard/api/custom-policies)
    var policies = [];
    var editingPolicyId = null; // null = not editing, 'new' = adding, string = editing existing

    function policySeverityLabel(v) {
      return v ? v.charAt(0).toUpperCase() + v.slice(1) : 'High';
    }

    function appendPolicyTags(container, policy) {
      var ap = policy.applies_to || 'both';
      var sev = policy.severity || 'high';
      if (ap === 'input' || ap === 'both') {
        var ti = document.createElement('span');
        ti.className = 'policy-tag policy-tag-scope';
        ti.textContent = 'Input';
        container.appendChild(ti);
      }
      if (ap === 'output' || ap === 'both') {
        var to = document.createElement('span');
        to.className = 'policy-tag policy-tag-scope';
        to.textContent = 'Output';
        container.appendChild(to);
      }
      var ts = document.createElement('span');
      ts.className = 'policy-tag policy-tag-sev-' + sev;
      ts.textContent = policySeverityLabel(sev);
      container.appendChild(ts);
    }

    function loadPolicies() {
      fetch('api/custom-policies')
        .then(function (r) {
          if (!r.ok) throw new Error('HTTP ' + r.status);
          return r.json();
        })
        .then(function (data) {
          policies = data.policies || [];
          renderPolicies();
          renderTesterPolicies();
        })
        .catch(function () {
          policies = [];
          var container = document.getElementById('policies-container');
          if (!container) return;
          container.textContent = '';
          var err = document.createElement('div');
          err.className = 'policy-empty';
          err.textContent = 'Policies could not be loaded from the server.';
          container.appendChild(err);
          renderTesterPolicies();
        });
    }

    function renderTesterPolicies() {
      var sel = document.getElementById('tester-policy');
      if (!sel) return;
      var prev = sel.value;
      sel.textContent = '';
      if (policies.length === 0) {
        var opt = document.createElement('option');
        opt.value = '';
        opt.textContent = 'No policies defined — add one above';
        sel.appendChild(opt);
        sel.disabled = true;
        return;
      }
      sel.disabled = false;
      policies.forEach(function (p) {
        var o = document.createElement('option');
        o.value = p.id;
        o.textContent = p.name + (p.enabled ? '' : ' (disabled)');
        sel.appendChild(o);
      });
      if (prev) sel.value = prev;
    }

    function runPolicyTest() {
      var sel = document.getElementById('tester-policy');
      var text = document.getElementById('tester-text').value;
      var resultEl = document.getElementById('tester-result');
      var btn = document.getElementById('tester-run');
      if (!sel || !sel.value) {
        resultEl.className = 'tester-result';
        resultEl.textContent = 'Select a policy first.';
        return;
      }
      resultEl.className = 'tester-result';
      resultEl.textContent = 'Evaluating…';
      btn.disabled = true;
      fetch('api/policy-test', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ policy_id: sel.value, text: text })
      }).then(function (r) {
        return r.json().then(function (body) { return { ok: r.ok, body: body }; });
      }).then(function (res) {
        if (!res.ok) {
          resultEl.className = 'tester-result';
          resultEl.textContent = (res.body && res.body.error) || 'Evaluation failed';
          return;
        }
        var passes = !!res.body.passes;
        resultEl.className = 'tester-result ' + (passes ? 'passed' : 'violated');
        resultEl.textContent = passes
          ? 'PASSED — text complies with "' + res.body.policy_name + '"'
          : 'VIOLATED — text breaks "' + res.body.policy_name + '" (' + res.body.severity + ' severity)';
      }).catch(function () {
        resultEl.className = 'tester-result';
        resultEl.textContent = 'Request failed. Is the proxy running?';
      }).then(function () {
        btn.disabled = false;
      });
    }

    function renderPolicies() {
      var container = document.getElementById('policies-container');
      if (!container) return;
      container.textContent = '';

      // Show form if editing
      if (editingPolicyId !== null) {
        var isNew = editingPolicyId === 'new';
        var existing = isNew ? null : policies.find(function (p) { return p.id === editingPolicyId; });

        var form = document.createElement('div');
        form.className = 'policy-form';

        var title = document.createElement('div');
        title.className = 'policy-form-title';
        title.textContent = isNew ? 'New Policy' : 'Edit Policy';
        form.appendChild(title);

        var nameGroup = document.createElement('div');
        nameGroup.className = 'policy-form-group';
        var nameLabel = document.createElement('label');
        nameLabel.className = 'policy-form-label';
        nameLabel.htmlFor = 'policy-form-name';
        nameLabel.textContent = 'Name';
        nameGroup.appendChild(nameLabel);
        var nameInput = document.createElement('input');
        nameInput.className = 'policy-form-input';
        nameInput.id = 'policy-form-name';
        nameInput.type = 'text';
        nameInput.placeholder = 'e.g. No PII in Logs';
        nameInput.value = existing ? existing.name : '';
        nameGroup.appendChild(nameInput);
        form.appendChild(nameGroup);

        var ruleGroup = document.createElement('div');
        ruleGroup.className = 'policy-form-group';
        var ruleLabel = document.createElement('label');
        ruleLabel.className = 'policy-form-label';
        ruleLabel.htmlFor = 'policy-form-rule';
        ruleLabel.textContent = 'Rule';
        ruleGroup.appendChild(ruleLabel);
        var ruleInput = document.createElement('textarea');
        ruleInput.className = 'policy-form-textarea';
        ruleInput.id = 'policy-form-rule';
        ruleInput.placeholder = 'Describe what this policy enforces...';
        ruleInput.value = existing ? existing.rule : '';
        ruleGroup.appendChild(ruleInput);
        form.appendChild(ruleGroup);

        var scopeGroup = document.createElement('div');
        scopeGroup.className = 'policy-form-group';
        var scopeLabelEl = document.createElement('label');
        scopeLabelEl.className = 'policy-form-label';
        scopeLabelEl.htmlFor = 'policy-form-applies-to';
        scopeLabelEl.textContent = 'Applies to';
        scopeGroup.appendChild(scopeLabelEl);
        var scopeSelect = document.createElement('select');
        scopeSelect.className = 'policy-form-input';
        scopeSelect.id = 'policy-form-applies-to';
        [['both', 'User input and model output'], ['input', 'User input only'], ['output', 'Model output only']].forEach(function (opt) {
          var o = document.createElement('option');
          o.value = opt[0];
          o.textContent = opt[1];
          scopeSelect.appendChild(o);
        });
        scopeSelect.value = existing && existing.applies_to ? existing.applies_to : 'both';
        scopeGroup.appendChild(scopeSelect);
        form.appendChild(scopeGroup);

        var sevGroup = document.createElement('div');
        sevGroup.className = 'policy-form-group';
        var sevLabelEl = document.createElement('label');
        sevLabelEl.className = 'policy-form-label';
        sevLabelEl.htmlFor = 'policy-form-severity';
        sevLabelEl.textContent = 'Severity (signal level)';
        sevGroup.appendChild(sevLabelEl);
        var sevSelect = document.createElement('select');
        sevSelect.className = 'policy-form-input';
        sevSelect.id = 'policy-form-severity';
        [['low', 'Low'], ['medium', 'Medium'], ['high', 'High']].forEach(function (opt) {
          var o = document.createElement('option');
          o.value = opt[0];
          o.textContent = opt[1];
          sevSelect.appendChild(o);
        });
        sevSelect.value = existing && existing.severity ? existing.severity : 'high';
        sevGroup.appendChild(sevSelect);
        form.appendChild(sevGroup);

        var btns = document.createElement('div');
        btns.className = 'policy-form-btns';
        var saveBtn = document.createElement('button');
        saveBtn.className = 'policy-form-save';
        saveBtn.textContent = isNew ? 'Create Policy' : 'Save Changes';
        saveBtn.onclick = function () { savePolicyForm(isNew, existing ? existing.id : null); };
        btns.appendChild(saveBtn);
        var cancelBtn = document.createElement('button');
        cancelBtn.className = 'policy-form-cancel';
        cancelBtn.textContent = 'Cancel';
        cancelBtn.onclick = function () { editingPolicyId = null; renderPolicies(); };
        btns.appendChild(cancelBtn);
        form.appendChild(btns);

        container.appendChild(form);

        // Focus the name input after render
        setTimeout(function () { nameInput.focus(); }, 30);
        return;
      }

      // Policy list
      if (policies.length === 0) {
        var emptyWrap = document.createElement('div');
        emptyWrap.className = 'policy-empty';
        emptyWrap.textContent = 'No policies defined';
        var hint = document.createElement('div');
        hint.className = 'policy-empty-hint';
        hint.textContent = 'Define rules that will be enforced on agent behavior.';
        emptyWrap.appendChild(hint);
        container.appendChild(emptyWrap);
      } else {
        var list = document.createElement('div');
        list.className = 'policies-list';

        policies.forEach(function (policy) {
          var item = document.createElement('div');
          item.className = 'policy-item' + (policy.enabled ? '' : ' disabled-policy');

          var nameEl = document.createElement('div');
          nameEl.className = 'policy-name';
          nameEl.textContent = policy.name;
          item.appendChild(nameEl);

          var ruleEl = document.createElement('div');
          ruleEl.className = 'policy-rule';
          ruleEl.textContent = policy.rule;
          item.appendChild(ruleEl);

          var tagsEl = document.createElement('div');
          tagsEl.className = 'policy-tags';
          appendPolicyTags(tagsEl, policy);
          item.appendChild(tagsEl);

          var actions = document.createElement('div');
          actions.className = 'policy-actions';

          // Toggle
          var toggle = document.createElement('div');
          toggle.className = 'policy-toggle-mini' + (policy.enabled ? ' on' : '');
          toggle.title = policy.enabled ? 'Disable' : 'Enable';
          toggle.setAttribute('role', 'switch');
          toggle.tabIndex = 0;
          toggle.setAttribute('aria-checked', policy.enabled ? 'true' : 'false');
          toggle.setAttribute('aria-label', (policy.enabled ? 'Disable ' : 'Enable ') + policy.name);
          toggle.onclick = function () { togglePolicy(policy.id); };
          toggle.onkeydown = function (event) {
            if (event.key === 'Enter' || event.key === ' ') {
              event.preventDefault();
              togglePolicy(policy.id);
            }
          };
          actions.appendChild(toggle);

          // Edit
          var editBtn = document.createElement('button');
          editBtn.className = 'policy-action-btn';
          editBtn.title = 'Edit';
          editBtn.innerHTML = '&#9998;';
          editBtn.onclick = function () { editingPolicyId = policy.id; renderPolicies(); };
          actions.appendChild(editBtn);

          // Delete
          var delBtn = document.createElement('button');
          delBtn.className = 'policy-action-btn delete';
          delBtn.title = 'Delete';
          delBtn.innerHTML = '&#10005;';
          delBtn.onclick = function () { deletePolicy(policy.id); };
          actions.appendChild(delBtn);

          item.appendChild(actions);
          list.appendChild(item);
        });

        container.appendChild(list);
      }

      // Add button
      var addBtn = document.createElement('button');
      addBtn.className = 'add-policy-btn';
      addBtn.onclick = function () { editingPolicyId = 'new'; renderPolicies(); };
      var plusIcon = document.createElement('span');
      plusIcon.className = 'plus-icon';
      plusIcon.textContent = '+';
      addBtn.appendChild(plusIcon);
      var addLabel = document.createElement('span');
      addLabel.textContent = 'Add Policy';
      addBtn.appendChild(addLabel);
      container.appendChild(addBtn);
    }

    function savePolicyForm(isNew, existingId) {
      var name = (document.getElementById('policy-form-name').value || '').trim();
      var rule = (document.getElementById('policy-form-rule').value || '').trim();
      var appliesTo = (document.getElementById('policy-form-applies-to') || {}).value || 'both';
      var severity = (document.getElementById('policy-form-severity') || {}).value || 'high';
      if (!name || !rule) return;

      if (isNew) {
        fetch('api/custom-policies', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            name: name,
            rule: rule,
            enabled: true,
            applies_to: appliesTo,
            severity: severity
          })
        })
          .then(function (r) {
            if (!r.ok) throw new Error();
            return r.json();
          })
          .then(function () {
            editingPolicyId = null;
            loadPolicies();
          })
          .catch(function () {
            alert('Failed to create policy');
          });
        return;
      }

      var existing = policies.find(function (p) { return p.id === existingId; });
      if (!existing) return;

      fetch('api/custom-policies/' + encodeURIComponent(existingId), {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: name,
          rule: rule,
          enabled: existing.enabled,
          applies_to: appliesTo,
          severity: severity
        })
      })
        .then(function (r) {
          if (!r.ok) throw new Error();
          return r.json();
        })
        .then(function () {
          editingPolicyId = null;
          loadPolicies();
        })
        .catch(function () {
          alert('Failed to save policy');
        });
    }

    function togglePolicy(id) {
      var p = policies.find(function (x) { return x.id === id; });
      if (!p) return;
      fetch('api/custom-policies/' + encodeURIComponent(id), {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: p.name,
          rule: p.rule,
          enabled: !p.enabled,
          applies_to: p.applies_to || 'both',
          severity: p.severity || 'high'
        })
      })
        .then(function (r) {
          if (!r.ok) throw new Error();
          return r.json();
        })
        .then(function () {
          loadPolicies();
        })
        .catch(function () {
          alert('Failed to update policy');
          loadPolicies();
        });
    }

    function deletePolicy(id) {
      fetch('api/custom-policies/' + encodeURIComponent(id), { method: 'DELETE' })
        .then(function (r) {
          if (r.status === 204) return;
          throw new Error();
        })
        .then(function () {
          if (editingPolicyId === id) editingPolicyId = null;
          loadPolicies();
        })
        .catch(function () {
          alert('Failed to delete policy');
        });
    }

    // Auto-select view on load
    setView(currentView);
    refresh();
    setInterval(refresh, 2000);
    loadPolicies();
  </script>
</body>

</html>
