File size: 8,829 Bytes
5f43c7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import React, { useState } from "react";
import { ShieldAlert } from "lucide-react";
import { C, FD, FB, FM, fmt } from "../theme.js";

// Small presentational atoms lifted verbatim-in-spirit from the validated mock.
// Do NOT restyle these; they carry the Tactical Grey look.

export function Chip({ dot, text }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 10.5, color: C.text2, fontFamily: FM, border: `1px solid ${C.borderSoft}`, borderRadius: 999, padding: "4px 10px" }}>
      <span style={{ width: 7, height: 7, borderRadius: 999, background: dot, boxShadow: `0 0 6px ${dot}` }} />
      {text}
    </div>
  );
}

export function Stat({ label, v, grad }) {
  return (
    <div style={{ textAlign: "right" }}>
      <div className={grad ? "hb" : ""} style={{ fontFamily: FM, fontSize: 14, fontWeight: 700, color: grad ? undefined : C.text }}>{v}</div>
      <div style={{ fontSize: 9, color: C.muted, letterSpacing: 0.5, textTransform: "uppercase" }}>{label}</div>
    </div>
  );
}

export function Hero({ v, label, grad }) {
  return (
    <div style={{ flex: 1, background: `linear-gradient(135deg,${C.card},${C.panel})`, border: `1px solid ${C.borderSoft}`, borderRadius: 10, padding: "13px 15px" }}>
      <div className={grad ? "hb" : ""} style={{ fontFamily: FD, fontWeight: 700, fontSize: 24, color: grad ? undefined : C.text }}>{v}</div>
      <div style={{ fontSize: 10, color: C.muted, letterSpacing: 0.6, textTransform: "uppercase", marginTop: 2 }}>{label}</div>
    </div>
  );
}

export function PanelTitle({ icon: Icon, text, mt }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 7, marginTop: mt ? 20 : 0, color: C.muted }}>
      <Icon size={13} />
      <span style={{ fontFamily: FM, fontSize: 10.5, letterSpacing: 0.6 }}>{text}</span>
    </div>
  );
}

export function SecHead({ icon: Icon, c, text }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 26 }}>
      <Icon size={16} color={c} />
      <span style={{ fontFamily: FM, fontSize: 11, letterSpacing: 0.9, color: c, fontWeight: 600 }}>{text}</span>
      <div style={{ flex: 1, height: 1, background: `linear-gradient(90deg,${c}44,transparent)` }} />
    </div>
  );
}

export function Bar({ label, v, max, c, note }) {
  return (
    <div style={{ marginTop: 9 }}>
      <div style={{ display: "flex", justifyContent: "space-between", fontSize: 11.5 }}>
        <span style={{ color: C.text2 }}>{label}</span>
        <span style={{ fontFamily: FM, color: c, fontWeight: 600 }}>{fmt(v)}</span>
      </div>
      <div style={{ height: 6, background: C.black, borderRadius: 3, marginTop: 4, overflow: "hidden" }}>
        <div style={{ width: `${Math.max(2, (100 * v) / Math.max(1, max))}%`, height: "100%", background: `linear-gradient(90deg,${c},${c}aa)` }} />
      </div>
      {note && <div style={{ fontSize: 9.5, color: C.muted, marginTop: 3, fontStyle: "italic" }}>{note}</div>}
    </div>
  );
}

// A line-style legend row used inside the turn graph (proven vs hypothesis).
export function EdgeLegend({ c, text, dashed }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 11, color: C.text2 }}>
      <span style={{ width: 16, height: 0, borderTop: `2px ${dashed ? "dotted" : "solid"} ${c}`, display: "inline-block" }} />
      {text}
    </div>
  );
}

export function ChainNode({ icon: Icon, c, title, sub }) {
  return (
    <div style={{ display: "flex", gap: 10, alignItems: "center", padding: "9px 11px", background: `linear-gradient(135deg,${C.card},${C.panel})`, border: `1px solid ${c}`, borderRadius: 8, marginBottom: 4, boxShadow: "0 1px 3px rgba(0,0,0,.35)" }}>
      <div style={{ width: 28, height: 28, borderRadius: 7, background: C.elevated, display: "flex", alignItems: "center", justifyContent: "center", border: `1px solid ${c}` }}>
        <Icon size={15} color={c} />
      </div>
      <div style={{ minWidth: 0 }}>
        <div style={{ fontSize: 13, fontWeight: 600, color: c }}>{title}</div>
        <div style={{ fontFamily: FM, fontSize: 10.5, color: C.muted, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", maxWidth: 520 }}>{sub}</div>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// BINARIES — the real tools run via Bash (npx remotion -> remotion, railway, …),
// a SEPARATE entity dimension from tool calls. Logo served LOCALLY from
// /binary-logos/<name>.svg (pulled by the background enricher); colored-monogram
// fallback when the file isn't there yet. No network on render (NN #2).
// ---------------------------------------------------------------------------
const _MONO = [C.orange, C.cyan, C.amber, C.blue, "#a78bfa", "#f472b6", "#34d399", "#fb923c", "#60a5fa"];
function monoColor(name) {
  let h = 0;
  for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
  return _MONO[h % _MONO.length];
}

export function BinaryLogo({ b, size = 18 }) {
  const [err, setErr] = useState(false);
  const name = (b.binary || b.name || "?");
  const src = b.logo || `/binary-logos/${encodeURIComponent(name)}.svg`;
  const col = monoColor(name);
  if (err) {
    return (
      <span style={{ width: size, height: size, borderRadius: 5, background: col + "22", border: `1px solid ${col}66`, color: col, display: "inline-flex", alignItems: "center", justifyContent: "center", fontFamily: FM, fontSize: Math.round(size * 0.5), fontWeight: 700, flexShrink: 0, lineHeight: 1 }}>
        {name[0] ? name[0].toUpperCase() : "?"}
      </span>
    );
  }
  return (
    <img src={src} alt="" width={size} height={size} onError={() => setErr(true)}
      style={{ borderRadius: 4, objectFit: "contain", flexShrink: 0, background: "transparent" }} />
  );
}

// Compact chip (logo + product) for session cards / inline lists.
export function BinaryBadge({ b, onClick }) {
  const name = b.binary || b.name;
  const label = b.product || name;
  return (
    <span onClick={onClick} className={onClick ? "lift" : ""}
      title={`${label}${b.blurb ? " — " + b.blurb : ""}${b.via && b.via !== "direct" ? " · via " + b.via : ""}${b.security ? "  ⚠ " + b.security : ""}`}
      style={{ display: "inline-flex", alignItems: "center", gap: 5, fontFamily: FM, fontSize: 9, color: C.text2, border: `1px solid ${C.borderSoft}`, borderRadius: 5, padding: "2px 6px 2px 4px", cursor: onClick ? "pointer" : "default" }}>
      <BinaryLogo b={b} size={12} />{label}
      {b.security && <ShieldAlert size={9} color={C.amber} />}
    </span>
  );
}

// A traceable row for the left rails: logo + product, blurb, count, security note,
// and the turns it fired in. `onOpen(turnIndex)` jumps to the first turn.
export function BinaryRow({ b, onOpen, color = C.orange }) {
  const name = b.binary || b.name;
  const label = b.product || name;
  const turns = b.turns || [];
  return (
    <div className="row lift"
      onClick={() => turns.length && onOpen && onOpen(turns[0])}
      title={`${b.identified ? label : name + " (not yet identified)"}${b.blurb ? " — " + b.blurb : ""}` +
        `${b.via && b.via !== "direct" ? " · via " + b.via : ""}` +
        `${turns.length ? " · turn(s) " + turns.join(", ") : ""}` +
        `${b.security ? "  ⚠ " + b.security : ""}`}
      style={{ display: "flex", alignItems: "center", gap: 7, padding: "5px 8px", borderRadius: 6, cursor: turns.length ? "pointer" : "default", borderLeft: `2px solid ${color}` }}>
      <BinaryLogo b={b} size={16} />
      <span style={{ minWidth: 0, flex: 1 }}>
        <span style={{ fontFamily: FM, fontSize: 11.5, color: C.text, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", display: "block" }}>
          {label}{!b.identified && <span style={{ color: C.muted }}> · {name}</span>}
        </span>
        {b.blurb && (
          <span style={{ fontFamily: FB, fontSize: 9.5, color: C.muted, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", display: "block" }}>{b.blurb}</span>
        )}
      </span>
      {b.security && <ShieldAlert size={11} color={C.amber} title={b.security} style={{ flexShrink: 0 }} />}
      <span style={{ fontFamily: FM, fontSize: 9, color: C.muted, flexShrink: 0 }}>×{b.count ?? b.total}</span>
    </div>
  );
}

// "Generated" provenance badge — every piece of narrator prose must be labelled
// generated (NON-NEGOTIABLE #7). Deterministic numbers are NOT badged.
export function GeneratedTag({ cites }) {
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 5, fontFamily: FM, fontSize: 9, letterSpacing: 0.5, color: C.muted, border: `1px solid ${C.borderSoft}`, borderRadius: 5, padding: "1px 7px" }}>
      GENERATED{cites ? ` · reads ${cites}` : ""}
    </span>
  );
}