@matterfact/embed 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -6
- package/dist/{chunk-523BCS2Z.js → chunk-GZZYR6YF.js} +2 -2
- package/dist/chunk-JN3ZMONO.js +2 -0
- package/dist/chunk-JN3ZMONO.js.map +7 -0
- package/dist/{chunk-Z4WT3TIZ.js → chunk-JSH3BRYP.js} +8 -31
- package/dist/chunk-JSH3BRYP.js.map +1 -0
- package/dist/{chunk-SBNMM3ZV.js → chunk-NZIN3LK7.js} +6 -30
- package/dist/chunk-NZIN3LK7.js.map +1 -0
- package/dist/chunk-Z53V2P4B.js +2 -0
- package/dist/context-AQY2LJWK.js +3 -0
- package/dist/{context-KZAUOIR6.js.map → context-AQY2LJWK.js.map} +1 -1
- package/dist/{context-432WQST4.js → context-UBTZEEWS.js} +2 -4
- package/dist/embed.js +1 -1
- package/dist/embed.js.map +2 -2
- package/dist/index.cjs +17 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +13 -14
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +17 -42
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +13 -14
- package/dist/react.js.map +1 -1
- package/dist/{snapshot-SOMSG2OF.js → snapshot-HYMRZTZC.js} +2 -2
- package/dist/{snapshot-Q3CALMHD.js → snapshot-XFCV2MYR.js} +3 -3
- package/dist/{snapshot-Q3CALMHD.js.map → snapshot-XFCV2MYR.js.map} +1 -1
- package/examples/README.md +46 -0
- package/examples/demo-host/index.html +163 -0
- package/examples/demo-host/serve.mjs +62 -0
- package/examples/embed-demo/.env.example +24 -0
- package/examples/embed-demo/README.md +72 -0
- package/examples/embed-demo/index.html +12 -0
- package/examples/embed-demo/package.json +25 -0
- package/examples/embed-demo/src/App.tsx +189 -0
- package/examples/embed-demo/src/config.ts +43 -0
- package/examples/embed-demo/src/main.tsx +10 -0
- package/examples/embed-demo/src/styles.css +354 -0
- package/examples/embed-demo/tsconfig.json +16 -0
- package/examples/embed-demo/vite.config.ts +11 -0
- package/examples/host-panel.html +237 -0
- package/package.json +3 -2
- package/dist/chunk-SBNMM3ZV.js.map +0 -1
- package/dist/chunk-URIBHOOQ.js +0 -2
- package/dist/chunk-XLQ36ZMV.js +0 -2
- package/dist/chunk-XLQ36ZMV.js.map +0 -7
- package/dist/chunk-Z4WT3TIZ.js.map +0 -1
- package/dist/context-KZAUOIR6.js +0 -3
- /package/dist/{chunk-523BCS2Z.js.map → chunk-GZZYR6YF.js.map} +0 -0
- /package/dist/{chunk-URIBHOOQ.js.map → chunk-Z53V2P4B.js.map} +0 -0
- /package/dist/{context-432WQST4.js.map → context-UBTZEEWS.js.map} +0 -0
- /package/dist/{snapshot-SOMSG2OF.js.map → snapshot-HYMRZTZC.js.map} +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# matterfact embed demo
|
|
2
|
+
|
|
3
|
+
A tiny React + Vite app that puts **both** matterfact embed surfaces on one page using
|
|
4
|
+
`@matterfact/embed`:
|
|
5
|
+
|
|
6
|
+
- **`<MatterfactAgent inline>`** — the chat, mounted in the page's right rail;
|
|
7
|
+
- **`<MatterfactArtifact>`** — a matterfact artifact embedded in the page.
|
|
8
|
+
|
|
9
|
+
Both on the page means the agent auto-detects the artifact by origin and can answer
|
|
10
|
+
questions about the data behind it — no wiring.
|
|
11
|
+
|
|
12
|
+
## Run it
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install
|
|
16
|
+
cp .env.example .env # fill in your key + artifact triple
|
|
17
|
+
npm run dev # → http://localhost:8000
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If anything's missing the app shows a setup screen naming the exact env vars still needed.
|
|
21
|
+
|
|
22
|
+
> Requires `@matterfact/embed` **>= 0.5.0** — earlier versions' loader auto-opens a stale
|
|
23
|
+
> sign-in popup that errors "Missing authorization parameters" on Entra deployments.
|
|
24
|
+
|
|
25
|
+
## Configure — everything lives in `.env`
|
|
26
|
+
|
|
27
|
+
Vite exposes only `VITE_`-prefixed vars to the app.
|
|
28
|
+
|
|
29
|
+
| Variable | What |
|
|
30
|
+
| --------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
31
|
+
| `VITE_MF_ENV` | Deployment: `ecm` \| `prod` \| `staging` \| `local`. Picks the widget origin. |
|
|
32
|
+
| `VITE_MF_WIDGET_ORIGIN` | Optional. Overrides the origin (required for `staging`). |
|
|
33
|
+
| `VITE_MF_PUBLISHABLE_KEY` | The agent's publishable key (`pk_live_…` / `pk_test_…`). |
|
|
34
|
+
| `VITE_MF_ARTIFACT_SLUG` / `_OWNER` / `_TOKEN` | The artifact — from its **Share → Embed** dialog. |
|
|
35
|
+
| `VITE_MF_SURFACE` | Free-form usage/history label. |
|
|
36
|
+
| `VITE_MF_THEME` | `light` \| `dark` \| `auto`. |
|
|
37
|
+
|
|
38
|
+
### Env → origin
|
|
39
|
+
|
|
40
|
+
| `VITE_MF_ENV` | Widget origin |
|
|
41
|
+
| ------------- | ------------------------------------------------------------------------- |
|
|
42
|
+
| `ecm` | `https://emc.matterfact.com` |
|
|
43
|
+
| `prod` | `https://app.matterfact.com` |
|
|
44
|
+
| `staging` | _(set `VITE_MF_WIDGET_ORIGIN`)_ |
|
|
45
|
+
| `local` | `http://localhost:3000` _(wherever your dev server serves `/embed/chat`)_ |
|
|
46
|
+
|
|
47
|
+
## The one gotcha: origin registration
|
|
48
|
+
|
|
49
|
+
The publishable key is **origin-pinned** (scheme + host + port, exact match). This app runs on
|
|
50
|
+
`http://localhost:8000` (fixed via `strictPort`). That exact origin must be registered as an
|
|
51
|
+
allowed origin on the key in the target deployment (e.g. minted against emc-api for `ecm`). If
|
|
52
|
+
it isn't, the agent iframe renders nothing — the key isn't wrong, the origin just isn't allowed.
|
|
53
|
+
|
|
54
|
+
## Signing in
|
|
55
|
+
|
|
56
|
+
The widget runs its own sign-in in a popup — a cross-site iframe has no ambient session, so
|
|
57
|
+
this is the honest production behaviour. Silent host-auth (`getAuthToken`) is out of scope here.
|
|
58
|
+
|
|
59
|
+
## Inspect what the agent sees
|
|
60
|
+
|
|
61
|
+
Add `?mfdev=1` to the URL — a panel shows exactly what the widget sends each turn.
|
|
62
|
+
|
|
63
|
+
## The two lines that matter
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { MatterfactAgent, MatterfactArtifact } from '@matterfact/embed/react';
|
|
67
|
+
|
|
68
|
+
<MatterfactAgent inline publishableKey="pk_live_…" widgetOrigin="https://emc.matterfact.com" />
|
|
69
|
+
<MatterfactArtifact slug="…" owner="you@firm.com" token="…" widgetOrigin="https://emc.matterfact.com" />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
See the package [README](../../README.md) for the full API.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Meridian Capital — matterfact embed demo</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "matterfact-embed-demo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "A tiny React app showing the matterfact embed agent + embed artifact, configured via .env.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "vite",
|
|
10
|
+
"build": "tsc --noEmit && vite build",
|
|
11
|
+
"preview": "vite preview --port 8000 --strictPort"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@matterfact/embed": "^0.5.0",
|
|
15
|
+
"react": "^18.3.1",
|
|
16
|
+
"react-dom": "^18.3.1"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/react": "^18.3.5",
|
|
20
|
+
"@types/react-dom": "^18.3.0",
|
|
21
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
22
|
+
"typescript": "^5.5.4",
|
|
23
|
+
"vite": "^5.4.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { MatterfactAgent, MatterfactArtifact } from '@matterfact/embed/react';
|
|
3
|
+
import { config, missingConfig } from './config';
|
|
4
|
+
|
|
5
|
+
export function App() {
|
|
6
|
+
const [panelOpen, setPanelOpen] = useState(true);
|
|
7
|
+
const missing = missingConfig();
|
|
8
|
+
|
|
9
|
+
if (missing.length) return <Setup missing={missing} />;
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="app" data-panel={panelOpen ? 'open' : 'closed'}>
|
|
13
|
+
<header>
|
|
14
|
+
<div className="brand">
|
|
15
|
+
<span className="mark">M</span> Meridian Capital{' '}
|
|
16
|
+
<small>· embed demo</small>
|
|
17
|
+
</div>
|
|
18
|
+
<nav>
|
|
19
|
+
<a href="#">Positions</a>
|
|
20
|
+
<a href="#" className="active">
|
|
21
|
+
Growth Sleeve
|
|
22
|
+
</a>
|
|
23
|
+
<a href="#">Research</a>
|
|
24
|
+
</nav>
|
|
25
|
+
<span className="spacer" />
|
|
26
|
+
<span className="env-chip">
|
|
27
|
+
{config.env} · {config.widgetOrigin.replace(/^https?:\/\//, '')}
|
|
28
|
+
</span>
|
|
29
|
+
<button className="toggle" onClick={() => setPanelOpen((v) => !v)}>
|
|
30
|
+
<span className="dot" /> Assistant
|
|
31
|
+
</button>
|
|
32
|
+
</header>
|
|
33
|
+
|
|
34
|
+
<div className="layout">
|
|
35
|
+
<main>
|
|
36
|
+
<div className="eyebrow">Portfolio</div>
|
|
37
|
+
<h1>Growth Sleeve</h1>
|
|
38
|
+
<div className="sub">
|
|
39
|
+
12 holdings · rebalanced 3 days ago · benchmark S&P 500
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div className="kpis">
|
|
43
|
+
<Kpi label="Market value" value="$48.2M" />
|
|
44
|
+
<Kpi label="Day change" value="+1.34%" tone="pos" />
|
|
45
|
+
<Kpi label="YTD" value="+18.7%" tone="pos" />
|
|
46
|
+
<Kpi label="vs benchmark" value="−2.1%" tone="neg" />
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div className="section-title">Embedded matterfact artifact</div>
|
|
50
|
+
<div className="artifact-frame">
|
|
51
|
+
<div className="artifact-badge">
|
|
52
|
+
<span className="mf">✦</span> matterfact artifact ·{' '}
|
|
53
|
+
<code>{config.artifact.slug}</code>
|
|
54
|
+
</div>
|
|
55
|
+
{/* The agent, mounted on the same page below, auto-detects this artifact
|
|
56
|
+
by origin and can answer questions about the data behind it. */}
|
|
57
|
+
<MatterfactArtifact
|
|
58
|
+
slug={config.artifact.slug}
|
|
59
|
+
owner={config.artifact.owner}
|
|
60
|
+
token={config.artifact.token}
|
|
61
|
+
widgetOrigin={config.widgetOrigin}
|
|
62
|
+
theme={config.theme}
|
|
63
|
+
style={{ width: '100%', height: '100%' }}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
<p className="artifact-caption">
|
|
67
|
+
<strong>Artifact-aware:</strong> the assistant detects this embedded
|
|
68
|
+
artifact by its origin and can pull the real data behind it — try
|
|
69
|
+
asking it to summarise the artifact.
|
|
70
|
+
</p>
|
|
71
|
+
|
|
72
|
+
<div className="section-title">Holdings</div>
|
|
73
|
+
<table>
|
|
74
|
+
<thead>
|
|
75
|
+
<tr>
|
|
76
|
+
<th>Ticker</th>
|
|
77
|
+
<th>Weight</th>
|
|
78
|
+
<th>Day</th>
|
|
79
|
+
<th>YTD</th>
|
|
80
|
+
<th>Thesis</th>
|
|
81
|
+
</tr>
|
|
82
|
+
</thead>
|
|
83
|
+
<tbody>
|
|
84
|
+
<Row
|
|
85
|
+
t="NVDA"
|
|
86
|
+
w="9.4%"
|
|
87
|
+
d="+2.8%"
|
|
88
|
+
y="+41%"
|
|
89
|
+
th="AI datacenter demand"
|
|
90
|
+
/>
|
|
91
|
+
<Row t="MSFT" w="8.1%" d="+0.9%" y="+22%" th="Copilot attach" />
|
|
92
|
+
<Row t="UNH" w="6.7%" d="−1.2%" y="−8%" th="MLR pressure" dn yn />
|
|
93
|
+
<Row
|
|
94
|
+
t="V"
|
|
95
|
+
w="5.9%"
|
|
96
|
+
d="+0.4%"
|
|
97
|
+
y="+11%"
|
|
98
|
+
th="Cross-border recovery"
|
|
99
|
+
/>
|
|
100
|
+
<Row t="LLY" w="5.2%" d="+1.7%" y="+34%" th="GLP-1 ramp" />
|
|
101
|
+
</tbody>
|
|
102
|
+
</table>
|
|
103
|
+
</main>
|
|
104
|
+
|
|
105
|
+
{/* THE PANEL — the host's box. The agent renders inline inside it, sized by CSS.
|
|
106
|
+
Showing/hiding this panel is the open/close control; no floating launcher. */}
|
|
107
|
+
<aside>
|
|
108
|
+
<div className="panel-head">
|
|
109
|
+
<span className="mf">✦</span> matterfact assistant
|
|
110
|
+
</div>
|
|
111
|
+
<div className="agent-slot">
|
|
112
|
+
<MatterfactAgent
|
|
113
|
+
inline
|
|
114
|
+
publishableKey={config.publishableKey}
|
|
115
|
+
widgetOrigin={config.widgetOrigin}
|
|
116
|
+
surface={config.surface}
|
|
117
|
+
theme={config.theme}
|
|
118
|
+
style={{ width: '100%', height: '100%' }}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
</aside>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function Kpi({
|
|
128
|
+
label,
|
|
129
|
+
value,
|
|
130
|
+
tone,
|
|
131
|
+
}: {
|
|
132
|
+
label: string;
|
|
133
|
+
value: string;
|
|
134
|
+
tone?: 'pos' | 'neg';
|
|
135
|
+
}) {
|
|
136
|
+
return (
|
|
137
|
+
<div className="kpi">
|
|
138
|
+
<div className="label">{label}</div>
|
|
139
|
+
<div className={`val ${tone ?? ''}`}>{value}</div>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function Row(props: {
|
|
145
|
+
t: string;
|
|
146
|
+
w: string;
|
|
147
|
+
d: string;
|
|
148
|
+
y: string;
|
|
149
|
+
th: string;
|
|
150
|
+
dn?: boolean;
|
|
151
|
+
yn?: boolean;
|
|
152
|
+
}) {
|
|
153
|
+
return (
|
|
154
|
+
<tr>
|
|
155
|
+
<td className="tick">{props.t}</td>
|
|
156
|
+
<td>{props.w}</td>
|
|
157
|
+
<td className={props.dn ? 'neg' : 'pos'}>{props.d}</td>
|
|
158
|
+
<td className={props.yn ? 'neg' : 'pos'}>{props.y}</td>
|
|
159
|
+
<td>{props.th}</td>
|
|
160
|
+
</tr>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function Setup({ missing }: { missing: string[] }) {
|
|
165
|
+
return (
|
|
166
|
+
<div className="setup">
|
|
167
|
+
<div className="box">
|
|
168
|
+
<h2>Finish the config first</h2>
|
|
169
|
+
<p>
|
|
170
|
+
Copy <code>.env.example</code> to <code>.env</code>, set the values
|
|
171
|
+
below, then restart
|
|
172
|
+
<code>npm run dev</code>.
|
|
173
|
+
</p>
|
|
174
|
+
<ul>
|
|
175
|
+
{missing.map((m) => (
|
|
176
|
+
<li key={m} className="bad">
|
|
177
|
+
✗ {m}
|
|
178
|
+
</li>
|
|
179
|
+
))}
|
|
180
|
+
</ul>
|
|
181
|
+
<p>
|
|
182
|
+
The publishable key must have <code>http://localhost:8000</code>{' '}
|
|
183
|
+
registered as an allowed origin on the target deployment, or the agent
|
|
184
|
+
renders nothing.
|
|
185
|
+
</p>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// All demo config comes from Vite env vars (VITE_*), so the components stay clean
|
|
2
|
+
// and nothing lives in the source. See .env.example.
|
|
3
|
+
|
|
4
|
+
type Theme = 'light' | 'dark' | 'auto';
|
|
5
|
+
|
|
6
|
+
// MF_WIDGET_ORIGIN always wins over this table.
|
|
7
|
+
const ORIGINS: Record<string, string> = {
|
|
8
|
+
ecm: 'https://emc.matterfact.com',
|
|
9
|
+
prod: 'https://app.matterfact.com',
|
|
10
|
+
staging: '', // no fixed host — set VITE_MF_WIDGET_ORIGIN
|
|
11
|
+
local: 'http://localhost:3000', // wherever your dev server serves /embed/chat
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const env = import.meta.env;
|
|
15
|
+
const envName = (env.VITE_MF_ENV ?? 'ecm').toLowerCase();
|
|
16
|
+
|
|
17
|
+
export const config = {
|
|
18
|
+
env: envName,
|
|
19
|
+
widgetOrigin: (env.VITE_MF_WIDGET_ORIGIN || ORIGINS[envName] || '').replace(
|
|
20
|
+
/\/+$/,
|
|
21
|
+
'',
|
|
22
|
+
),
|
|
23
|
+
publishableKey: env.VITE_MF_PUBLISHABLE_KEY ?? '',
|
|
24
|
+
artifact: {
|
|
25
|
+
slug: env.VITE_MF_ARTIFACT_SLUG ?? '',
|
|
26
|
+
owner: env.VITE_MF_ARTIFACT_OWNER ?? '',
|
|
27
|
+
token: env.VITE_MF_ARTIFACT_TOKEN ?? '',
|
|
28
|
+
},
|
|
29
|
+
surface: env.VITE_MF_SURFACE ?? 'embed-demo',
|
|
30
|
+
theme: (env.VITE_MF_THEME ?? 'light') as Theme,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Which required values are still missing — drives the setup screen. */
|
|
34
|
+
export function missingConfig(): string[] {
|
|
35
|
+
const miss: string[] = [];
|
|
36
|
+
if (!config.widgetOrigin)
|
|
37
|
+
miss.push('VITE_MF_ENV / VITE_MF_WIDGET_ORIGIN (widget origin)');
|
|
38
|
+
if (!config.publishableKey) miss.push('VITE_MF_PUBLISHABLE_KEY (agent key)');
|
|
39
|
+
if (!config.artifact.slug) miss.push('VITE_MF_ARTIFACT_SLUG');
|
|
40
|
+
if (!config.artifact.owner) miss.push('VITE_MF_ARTIFACT_OWNER');
|
|
41
|
+
if (!config.artifact.token) miss.push('VITE_MF_ARTIFACT_TOKEN');
|
|
42
|
+
return miss;
|
|
43
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #f4f6f9;
|
|
3
|
+
--surface: #fff;
|
|
4
|
+
--surface-2: #f0f3f7;
|
|
5
|
+
--line: #dfe4ec;
|
|
6
|
+
--ink: #131722;
|
|
7
|
+
--muted: #616b7c;
|
|
8
|
+
--accent: #2f5fe0;
|
|
9
|
+
--pos: #128a5b;
|
|
10
|
+
--neg: #c2453d;
|
|
11
|
+
--panel-w: 420px;
|
|
12
|
+
--sans:
|
|
13
|
+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
|
|
14
|
+
sans-serif;
|
|
15
|
+
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
16
|
+
}
|
|
17
|
+
@media (prefers-color-scheme: dark) {
|
|
18
|
+
:root {
|
|
19
|
+
--bg: #0f1419;
|
|
20
|
+
--surface: #161c24;
|
|
21
|
+
--surface-2: #1d2530;
|
|
22
|
+
--line: #28303c;
|
|
23
|
+
--ink: #e7ecf2;
|
|
24
|
+
--muted: #8b97a6;
|
|
25
|
+
--accent: #6b93ff;
|
|
26
|
+
--pos: #4ea88a;
|
|
27
|
+
--neg: #d1706a;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
* {
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
}
|
|
33
|
+
html,
|
|
34
|
+
body,
|
|
35
|
+
#root {
|
|
36
|
+
height: 100%;
|
|
37
|
+
margin: 0;
|
|
38
|
+
}
|
|
39
|
+
body {
|
|
40
|
+
background: var(--bg);
|
|
41
|
+
color: var(--ink);
|
|
42
|
+
font-family: var(--sans);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.app {
|
|
46
|
+
display: grid;
|
|
47
|
+
grid-template-rows: auto 1fr;
|
|
48
|
+
height: 100vh;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
header {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 16px;
|
|
56
|
+
padding: 0 20px;
|
|
57
|
+
height: 56px;
|
|
58
|
+
border-bottom: 1px solid var(--line);
|
|
59
|
+
background: var(--surface);
|
|
60
|
+
}
|
|
61
|
+
.brand {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 10px;
|
|
65
|
+
font-weight: 700;
|
|
66
|
+
letter-spacing: -0.01em;
|
|
67
|
+
}
|
|
68
|
+
.brand .mark {
|
|
69
|
+
width: 26px;
|
|
70
|
+
height: 26px;
|
|
71
|
+
border-radius: 7px;
|
|
72
|
+
background: linear-gradient(135deg, var(--accent), #1b3fa8);
|
|
73
|
+
display: grid;
|
|
74
|
+
place-items: center;
|
|
75
|
+
color: #fff;
|
|
76
|
+
font-weight: 800;
|
|
77
|
+
font-size: 15px;
|
|
78
|
+
}
|
|
79
|
+
.brand small {
|
|
80
|
+
color: var(--muted);
|
|
81
|
+
font-weight: 500;
|
|
82
|
+
margin-left: 2px;
|
|
83
|
+
}
|
|
84
|
+
nav {
|
|
85
|
+
display: flex;
|
|
86
|
+
gap: 4px;
|
|
87
|
+
margin-left: 12px;
|
|
88
|
+
}
|
|
89
|
+
nav a {
|
|
90
|
+
color: var(--muted);
|
|
91
|
+
text-decoration: none;
|
|
92
|
+
font-size: 13.5px;
|
|
93
|
+
font-weight: 500;
|
|
94
|
+
padding: 6px 11px;
|
|
95
|
+
border-radius: 7px;
|
|
96
|
+
}
|
|
97
|
+
nav a.active {
|
|
98
|
+
color: var(--ink);
|
|
99
|
+
background: var(--surface-2);
|
|
100
|
+
}
|
|
101
|
+
.spacer {
|
|
102
|
+
flex: 1;
|
|
103
|
+
}
|
|
104
|
+
.env-chip {
|
|
105
|
+
font-family: var(--mono);
|
|
106
|
+
font-size: 11.5px;
|
|
107
|
+
color: var(--muted);
|
|
108
|
+
border: 1px solid var(--line);
|
|
109
|
+
border-radius: 999px;
|
|
110
|
+
padding: 4px 10px;
|
|
111
|
+
}
|
|
112
|
+
.toggle {
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
gap: 8px;
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
background: var(--surface-2);
|
|
118
|
+
color: var(--ink);
|
|
119
|
+
border: 1px solid var(--line);
|
|
120
|
+
border-radius: 8px;
|
|
121
|
+
padding: 7px 12px;
|
|
122
|
+
font: inherit;
|
|
123
|
+
font-size: 13px;
|
|
124
|
+
font-weight: 600;
|
|
125
|
+
}
|
|
126
|
+
.toggle:hover {
|
|
127
|
+
border-color: var(--accent);
|
|
128
|
+
}
|
|
129
|
+
.toggle .dot {
|
|
130
|
+
width: 7px;
|
|
131
|
+
height: 7px;
|
|
132
|
+
border-radius: 50%;
|
|
133
|
+
background: var(--pos);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.layout {
|
|
137
|
+
display: grid;
|
|
138
|
+
grid-template-columns: 1fr var(--panel-w);
|
|
139
|
+
min-height: 0;
|
|
140
|
+
}
|
|
141
|
+
.app[data-panel='closed'] .layout {
|
|
142
|
+
grid-template-columns: 1fr 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
main {
|
|
146
|
+
overflow-y: auto;
|
|
147
|
+
padding: 28px 32px 40px;
|
|
148
|
+
min-width: 0;
|
|
149
|
+
}
|
|
150
|
+
.eyebrow {
|
|
151
|
+
font-family: var(--mono);
|
|
152
|
+
font-size: 11.5px;
|
|
153
|
+
letter-spacing: 0.08em;
|
|
154
|
+
text-transform: uppercase;
|
|
155
|
+
color: var(--muted);
|
|
156
|
+
}
|
|
157
|
+
h1 {
|
|
158
|
+
font-size: 26px;
|
|
159
|
+
letter-spacing: -0.02em;
|
|
160
|
+
margin: 6px 0 2px;
|
|
161
|
+
}
|
|
162
|
+
.sub {
|
|
163
|
+
color: var(--muted);
|
|
164
|
+
font-size: 14px;
|
|
165
|
+
margin-bottom: 26px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.kpis {
|
|
169
|
+
display: grid;
|
|
170
|
+
grid-template-columns: repeat(4, 1fr);
|
|
171
|
+
gap: 12px;
|
|
172
|
+
margin-bottom: 26px;
|
|
173
|
+
}
|
|
174
|
+
.kpi {
|
|
175
|
+
background: var(--surface);
|
|
176
|
+
border: 1px solid var(--line);
|
|
177
|
+
border-radius: 12px;
|
|
178
|
+
padding: 14px 16px;
|
|
179
|
+
}
|
|
180
|
+
.kpi .label {
|
|
181
|
+
font-size: 11.5px;
|
|
182
|
+
color: var(--muted);
|
|
183
|
+
text-transform: uppercase;
|
|
184
|
+
letter-spacing: 0.05em;
|
|
185
|
+
}
|
|
186
|
+
.kpi .val {
|
|
187
|
+
font-size: 22px;
|
|
188
|
+
font-weight: 700;
|
|
189
|
+
margin-top: 6px;
|
|
190
|
+
font-variant-numeric: tabular-nums;
|
|
191
|
+
}
|
|
192
|
+
.kpi .val.pos {
|
|
193
|
+
color: var(--pos);
|
|
194
|
+
}
|
|
195
|
+
.kpi .val.neg {
|
|
196
|
+
color: var(--neg);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.section-title {
|
|
200
|
+
font-size: 13px;
|
|
201
|
+
font-weight: 700;
|
|
202
|
+
color: var(--muted);
|
|
203
|
+
text-transform: uppercase;
|
|
204
|
+
letter-spacing: 0.05em;
|
|
205
|
+
margin: 4px 0 12px;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.artifact-frame {
|
|
209
|
+
position: relative;
|
|
210
|
+
width: 100%;
|
|
211
|
+
height: 360px;
|
|
212
|
+
border: 1px solid var(--line);
|
|
213
|
+
border-radius: 12px;
|
|
214
|
+
background: var(--surface-2);
|
|
215
|
+
margin-bottom: 8px;
|
|
216
|
+
overflow: hidden;
|
|
217
|
+
}
|
|
218
|
+
.artifact-badge {
|
|
219
|
+
position: absolute;
|
|
220
|
+
top: 12px;
|
|
221
|
+
left: 12px;
|
|
222
|
+
z-index: 2;
|
|
223
|
+
display: flex;
|
|
224
|
+
align-items: center;
|
|
225
|
+
gap: 7px;
|
|
226
|
+
background: color-mix(in srgb, var(--surface) 82%, transparent);
|
|
227
|
+
backdrop-filter: blur(4px);
|
|
228
|
+
border: 1px solid var(--line);
|
|
229
|
+
border-radius: 999px;
|
|
230
|
+
padding: 5px 11px;
|
|
231
|
+
font-size: 11.5px;
|
|
232
|
+
color: var(--muted);
|
|
233
|
+
}
|
|
234
|
+
.artifact-badge .mf {
|
|
235
|
+
color: var(--accent);
|
|
236
|
+
}
|
|
237
|
+
.artifact-badge code {
|
|
238
|
+
color: var(--ink);
|
|
239
|
+
font-family: var(--mono);
|
|
240
|
+
font-size: 11px;
|
|
241
|
+
}
|
|
242
|
+
.artifact-caption {
|
|
243
|
+
font-size: 12.5px;
|
|
244
|
+
color: var(--muted);
|
|
245
|
+
margin: 0 0 26px;
|
|
246
|
+
}
|
|
247
|
+
.artifact-caption strong {
|
|
248
|
+
color: var(--ink);
|
|
249
|
+
font-weight: 600;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
table {
|
|
253
|
+
width: 100%;
|
|
254
|
+
border-collapse: collapse;
|
|
255
|
+
font-size: 13.5px;
|
|
256
|
+
}
|
|
257
|
+
th {
|
|
258
|
+
text-align: left;
|
|
259
|
+
color: var(--muted);
|
|
260
|
+
font-weight: 600;
|
|
261
|
+
font-size: 11.5px;
|
|
262
|
+
text-transform: uppercase;
|
|
263
|
+
letter-spacing: 0.04em;
|
|
264
|
+
padding: 8px 10px;
|
|
265
|
+
border-bottom: 1px solid var(--line);
|
|
266
|
+
}
|
|
267
|
+
td {
|
|
268
|
+
padding: 11px 10px;
|
|
269
|
+
border-bottom: 1px solid var(--line);
|
|
270
|
+
font-variant-numeric: tabular-nums;
|
|
271
|
+
}
|
|
272
|
+
td.tick {
|
|
273
|
+
font-weight: 700;
|
|
274
|
+
}
|
|
275
|
+
td.pos {
|
|
276
|
+
color: var(--pos);
|
|
277
|
+
}
|
|
278
|
+
td.neg {
|
|
279
|
+
color: var(--neg);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
aside {
|
|
283
|
+
border-left: 1px solid var(--line);
|
|
284
|
+
background: var(--surface);
|
|
285
|
+
display: flex;
|
|
286
|
+
flex-direction: column;
|
|
287
|
+
min-width: 0;
|
|
288
|
+
overflow: hidden;
|
|
289
|
+
}
|
|
290
|
+
.panel-head {
|
|
291
|
+
display: flex;
|
|
292
|
+
align-items: center;
|
|
293
|
+
gap: 8px;
|
|
294
|
+
height: 44px;
|
|
295
|
+
padding: 0 14px;
|
|
296
|
+
border-bottom: 1px solid var(--line);
|
|
297
|
+
font-size: 12.5px;
|
|
298
|
+
font-weight: 700;
|
|
299
|
+
color: var(--muted);
|
|
300
|
+
text-transform: uppercase;
|
|
301
|
+
letter-spacing: 0.05em;
|
|
302
|
+
}
|
|
303
|
+
.panel-head .mf {
|
|
304
|
+
color: var(--accent);
|
|
305
|
+
}
|
|
306
|
+
.agent-slot {
|
|
307
|
+
flex: 1;
|
|
308
|
+
min-height: 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.setup {
|
|
312
|
+
display: grid;
|
|
313
|
+
place-items: center;
|
|
314
|
+
height: 100vh;
|
|
315
|
+
padding: 24px;
|
|
316
|
+
}
|
|
317
|
+
.setup .box {
|
|
318
|
+
max-width: 580px;
|
|
319
|
+
background: var(--surface);
|
|
320
|
+
border: 1px solid var(--line);
|
|
321
|
+
border-radius: 14px;
|
|
322
|
+
padding: 28px 30px;
|
|
323
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12);
|
|
324
|
+
}
|
|
325
|
+
.setup h2 {
|
|
326
|
+
margin: 0 0 6px;
|
|
327
|
+
font-size: 18px;
|
|
328
|
+
}
|
|
329
|
+
.setup p {
|
|
330
|
+
color: var(--muted);
|
|
331
|
+
font-size: 14px;
|
|
332
|
+
line-height: 1.55;
|
|
333
|
+
margin: 10px 0;
|
|
334
|
+
}
|
|
335
|
+
.setup code {
|
|
336
|
+
font-family: var(--mono);
|
|
337
|
+
font-size: 12.5px;
|
|
338
|
+
background: var(--surface-2);
|
|
339
|
+
padding: 2px 6px;
|
|
340
|
+
border-radius: 5px;
|
|
341
|
+
color: var(--ink);
|
|
342
|
+
margin: 0 2px;
|
|
343
|
+
}
|
|
344
|
+
.setup ul {
|
|
345
|
+
margin: 8px 0 0;
|
|
346
|
+
padding-left: 18px;
|
|
347
|
+
}
|
|
348
|
+
.setup li {
|
|
349
|
+
font-size: 13.5px;
|
|
350
|
+
line-height: 1.7;
|
|
351
|
+
}
|
|
352
|
+
.setup li.bad {
|
|
353
|
+
color: var(--neg);
|
|
354
|
+
}
|