@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.
Files changed (52) hide show
  1. package/README.md +26 -6
  2. package/dist/{chunk-523BCS2Z.js → chunk-GZZYR6YF.js} +2 -2
  3. package/dist/chunk-JN3ZMONO.js +2 -0
  4. package/dist/chunk-JN3ZMONO.js.map +7 -0
  5. package/dist/{chunk-Z4WT3TIZ.js → chunk-JSH3BRYP.js} +8 -31
  6. package/dist/chunk-JSH3BRYP.js.map +1 -0
  7. package/dist/{chunk-SBNMM3ZV.js → chunk-NZIN3LK7.js} +6 -30
  8. package/dist/chunk-NZIN3LK7.js.map +1 -0
  9. package/dist/chunk-Z53V2P4B.js +2 -0
  10. package/dist/context-AQY2LJWK.js +3 -0
  11. package/dist/{context-KZAUOIR6.js.map → context-AQY2LJWK.js.map} +1 -1
  12. package/dist/{context-432WQST4.js → context-UBTZEEWS.js} +2 -4
  13. package/dist/embed.js +1 -1
  14. package/dist/embed.js.map +2 -2
  15. package/dist/index.cjs +17 -42
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +7 -3
  18. package/dist/index.d.ts +7 -3
  19. package/dist/index.js +13 -14
  20. package/dist/index.js.map +1 -1
  21. package/dist/react.cjs +17 -42
  22. package/dist/react.cjs.map +1 -1
  23. package/dist/react.js +13 -14
  24. package/dist/react.js.map +1 -1
  25. package/dist/{snapshot-SOMSG2OF.js → snapshot-HYMRZTZC.js} +2 -2
  26. package/dist/{snapshot-Q3CALMHD.js → snapshot-XFCV2MYR.js} +3 -3
  27. package/dist/{snapshot-Q3CALMHD.js.map → snapshot-XFCV2MYR.js.map} +1 -1
  28. package/examples/README.md +46 -0
  29. package/examples/demo-host/index.html +163 -0
  30. package/examples/demo-host/serve.mjs +62 -0
  31. package/examples/embed-demo/.env.example +24 -0
  32. package/examples/embed-demo/README.md +72 -0
  33. package/examples/embed-demo/index.html +12 -0
  34. package/examples/embed-demo/package.json +25 -0
  35. package/examples/embed-demo/src/App.tsx +189 -0
  36. package/examples/embed-demo/src/config.ts +43 -0
  37. package/examples/embed-demo/src/main.tsx +10 -0
  38. package/examples/embed-demo/src/styles.css +354 -0
  39. package/examples/embed-demo/tsconfig.json +16 -0
  40. package/examples/embed-demo/vite.config.ts +11 -0
  41. package/examples/host-panel.html +237 -0
  42. package/package.json +3 -2
  43. package/dist/chunk-SBNMM3ZV.js.map +0 -1
  44. package/dist/chunk-URIBHOOQ.js +0 -2
  45. package/dist/chunk-XLQ36ZMV.js +0 -2
  46. package/dist/chunk-XLQ36ZMV.js.map +0 -7
  47. package/dist/chunk-Z4WT3TIZ.js.map +0 -1
  48. package/dist/context-KZAUOIR6.js +0 -3
  49. /package/dist/{chunk-523BCS2Z.js.map → chunk-GZZYR6YF.js.map} +0 -0
  50. /package/dist/{chunk-URIBHOOQ.js.map → chunk-Z53V2P4B.js.map} +0 -0
  51. /package/dist/{context-432WQST4.js.map → context-UBTZEEWS.js.map} +0 -0
  52. /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&amp;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,10 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { App } from './App';
4
+ import './styles.css';
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
@@ -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
+ }