dev-api-ui 0.1.7
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 +36 -0
- package/package.json +40 -0
- package/ui/ThemeProvider.tsx +16 -0
- package/ui/components/ArticlePage.tsx +673 -0
- package/ui/components/AuthPage.tsx +109 -0
- package/ui/components/Callout.tsx +79 -0
- package/ui/components/Chart.tsx +100 -0
- package/ui/components/CodeTabs.tsx +145 -0
- package/ui/components/ComparePage.tsx +364 -0
- package/ui/components/DashboardPage.tsx +773 -0
- package/ui/components/DocsNav.tsx +80 -0
- package/ui/components/Footer.tsx +136 -0
- package/ui/components/HubPage.tsx +529 -0
- package/ui/components/Modal.tsx +412 -0
- package/ui/components/Nav.tsx +162 -0
- package/ui/components/OnThisPage.tsx +56 -0
- package/ui/components/ParamsTable.tsx +86 -0
- package/ui/components/RangeBar.tsx +68 -0
- package/ui/components/SeriesChart.tsx +218 -0
- package/ui/components/SeriesPage.tsx +461 -0
- package/ui/components/StatusMark.tsx +48 -0
- package/ui/components/publictrades/ExternalLink.tsx +37 -0
- package/ui/components/publictrades/FactsCard.tsx +40 -0
- package/ui/components/publictrades/LedgerFooter.tsx +22 -0
- package/ui/components/publictrades/LedgerNav.tsx +78 -0
- package/ui/components/publictrades/Mark.tsx +40 -0
- package/ui/components/publictrades/SourceBlock.tsx +91 -0
- package/ui/components/publictrades/Tape.tsx +164 -0
- package/ui/components/publictrades/ThreeStamps.tsx +47 -0
- package/ui/components/publictrades/types.ts +19 -0
- package/ui/data/series.ts +851 -0
- package/ui/index.ts +50 -0
- package/ui/publictrades.ts +16 -0
- package/ui/themes.css +81 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
// Static — no client state needed. All data derived from the `rival` prop.
|
|
2
|
+
|
|
3
|
+
interface CmpRow {
|
|
4
|
+
feat: string;
|
|
5
|
+
aText: string;
|
|
6
|
+
aColor: string;
|
|
7
|
+
bText: string;
|
|
8
|
+
bColor: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface RelatedCard {
|
|
12
|
+
type: string;
|
|
13
|
+
title: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ACCENT = '#1F8A5B';
|
|
17
|
+
const DOWN = '#C2453B';
|
|
18
|
+
const SEC = '#5B6470';
|
|
19
|
+
const TERT = '#8A8F98';
|
|
20
|
+
|
|
21
|
+
function renderCell(v: string, isUs: boolean): { text: string; color: string } {
|
|
22
|
+
if (v === 'check') return { text: '✓ Yes', color: ACCENT };
|
|
23
|
+
if (v === 'cross') return { text: '✗ No', color: DOWN };
|
|
24
|
+
return { text: v, color: isUs ? ACCENT : SEC };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ROW_DEFS: [string, string, string][] = [
|
|
28
|
+
['Point-in-time history', 'check', 'manual'],
|
|
29
|
+
['Per-value source provenance', 'check', 'cross'],
|
|
30
|
+
['REST + WebSocket API', 'check', 'terminal only'],
|
|
31
|
+
['Public, per-call pricing', 'check', 'quote-based'],
|
|
32
|
+
['Free tier', 'check', 'cross'],
|
|
33
|
+
['One schema across fx · metals · power', 'check', 'siloed'],
|
|
34
|
+
['CSV / Parquet export', 'check', 'limited'],
|
|
35
|
+
['Time to first call', 'minutes', 'weeks'],
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
function buildRows(): CmpRow[] {
|
|
39
|
+
return ROW_DEFS.map(([feat, a, b]) => {
|
|
40
|
+
const ca = renderCell(a, true);
|
|
41
|
+
const cb = renderCell(b, false);
|
|
42
|
+
return { feat, aText: ca.text, aColor: ca.color, bText: cb.text, bColor: cb.color };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── External link glyph ────────────────────────────────────────────────────────
|
|
47
|
+
function ExtIcon() {
|
|
48
|
+
return (
|
|
49
|
+
<svg viewBox="0 0 24 24" width={13} height={13} fill="none" stroke="currentColor"
|
|
50
|
+
strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
51
|
+
<path d="M13 5h6v6" />
|
|
52
|
+
<path d="M19 5l-8 8" />
|
|
53
|
+
<path d="M19 13v5a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h5" />
|
|
54
|
+
</svg>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Arrow icon ─────────────────────────────────────────────────────────────────
|
|
59
|
+
function ArrowRight() {
|
|
60
|
+
return (
|
|
61
|
+
<svg viewBox="0 0 24 24" width={13} height={13} fill="none" stroke="currentColor"
|
|
62
|
+
strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
63
|
+
<path d="M5 12h14" />
|
|
64
|
+
<path d="M13 6l6 6-6 6" />
|
|
65
|
+
</svg>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Page component ─────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
interface ComparePageProps {
|
|
72
|
+
rival: string;
|
|
73
|
+
brand?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default function ComparePage({ rival, brand = 'Console' }: ComparePageProps) {
|
|
77
|
+
const rivalCap = rival.charAt(0).toUpperCase() + rival.slice(1);
|
|
78
|
+
const rows = buildRows();
|
|
79
|
+
const related: RelatedCard[] = [
|
|
80
|
+
{ type: 'Compare', title: `${brand} vs scraped feeds` },
|
|
81
|
+
{ type: 'Compare', title: `${brand} vs spreadsheet exports` },
|
|
82
|
+
{ type: 'Guide', title: 'Migrating a backtest off restated data' },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div style={{ background: 'var(--paper)', minHeight: '100vh' }}>
|
|
87
|
+
<div style={{ maxWidth: 920, margin: '0 auto', padding: '0 32px' }}>
|
|
88
|
+
|
|
89
|
+
{/* ── Header / lede ─────────────────────────────────────────────── */}
|
|
90
|
+
<section style={{ padding: '34px 0 26px', borderBottom: '0.5px solid var(--hairline)' }}>
|
|
91
|
+
{/* Breadcrumb */}
|
|
92
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, color: TERT }}>
|
|
93
|
+
<a href="#" style={{ color: SEC, textDecoration: 'none' }}>Compare</a>
|
|
94
|
+
<span>/</span>
|
|
95
|
+
<span style={{ color: 'var(--ink)' }}>{brand} vs {rivalCap}</span>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* Eyebrow */}
|
|
99
|
+
<div style={{
|
|
100
|
+
display: 'inline-flex', alignItems: 'center', gap: 8,
|
|
101
|
+
fontSize: 12, fontWeight: 600, letterSpacing: '.04em',
|
|
102
|
+
textTransform: 'uppercase', color: 'var(--accent)',
|
|
103
|
+
marginTop: 18,
|
|
104
|
+
}}>
|
|
105
|
+
<span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--accent)', display: 'inline-block' }} />
|
|
106
|
+
Comparison
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{/* H1 */}
|
|
110
|
+
<h1 style={{
|
|
111
|
+
fontFamily: 'var(--mono)', fontSize: 34, fontWeight: 600,
|
|
112
|
+
letterSpacing: '-.02em', marginTop: 12, lineHeight: 1.12,
|
|
113
|
+
}}>
|
|
114
|
+
{brand} vs {rivalCap}
|
|
115
|
+
</h1>
|
|
116
|
+
|
|
117
|
+
{/* Verdict lede */}
|
|
118
|
+
<p style={{ fontSize: 18, color: 'var(--ink)', lineHeight: 1.6, marginTop: 14 }}>
|
|
119
|
+
<b style={{ fontWeight: 600 }}>
|
|
120
|
+
For programmatic, reproducible market data, {brand} wins on provenance, price, and time-to-first-call.
|
|
121
|
+
</b>{' '}
|
|
122
|
+
A {rival.toLowerCase()} still leads if you need a manned terminal with news, chat, and execution in one seat.
|
|
123
|
+
</p>
|
|
124
|
+
|
|
125
|
+
{/* Last-reviewed note */}
|
|
126
|
+
<div style={{ fontSize: 13, color: TERT, marginTop: 14 }}>
|
|
127
|
+
Last reviewed Jun 2026 · we update this page when either product ships material changes.
|
|
128
|
+
</div>
|
|
129
|
+
</section>
|
|
130
|
+
|
|
131
|
+
{/* ── Positioning cards (side-by-side — short data cards) ───────── */}
|
|
132
|
+
<section style={{ padding: '28px 0', borderBottom: '0.5px solid var(--hairline)' }}>
|
|
133
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, alignItems: 'stretch' }}>
|
|
134
|
+
|
|
135
|
+
{/* Console card */}
|
|
136
|
+
<div style={{
|
|
137
|
+
background: 'var(--surface)',
|
|
138
|
+
border: '0.5px solid var(--accent)',
|
|
139
|
+
borderTop: '3px solid var(--accent)',
|
|
140
|
+
borderRadius: 8,
|
|
141
|
+
padding: '20px 22px',
|
|
142
|
+
display: 'flex', flexDirection: 'column', gap: 12,
|
|
143
|
+
}}>
|
|
144
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
145
|
+
<span style={{ fontFamily: 'var(--mono)', fontSize: 16, fontWeight: 600 }}>{brand}</span>
|
|
146
|
+
<span style={{
|
|
147
|
+
fontSize: 11, fontWeight: 600, color: '#fff',
|
|
148
|
+
background: 'var(--accent)', borderRadius: 999, padding: '3px 11px',
|
|
149
|
+
}}>
|
|
150
|
+
Best for builders
|
|
151
|
+
</span>
|
|
152
|
+
</div>
|
|
153
|
+
<div style={{ fontSize: 14, color: SEC, lineHeight: 1.5 }}>
|
|
154
|
+
A programmatic data API. Point-in-time by default, every value sourced, public pricing.
|
|
155
|
+
</div>
|
|
156
|
+
<div style={{ fontSize: 13, color: TERT }}>
|
|
157
|
+
From <span style={{ color: 'var(--ink)', fontWeight: 500 }}>$0</span> · free tier, no card
|
|
158
|
+
</div>
|
|
159
|
+
<a href="#" style={{
|
|
160
|
+
marginTop: 'auto',
|
|
161
|
+
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 7,
|
|
162
|
+
fontSize: 14, fontWeight: 500,
|
|
163
|
+
padding: '9px 15px',
|
|
164
|
+
borderRadius: 4,
|
|
165
|
+
border: '0.5px solid transparent',
|
|
166
|
+
background: 'var(--accent)',
|
|
167
|
+
color: '#fff',
|
|
168
|
+
textDecoration: 'none',
|
|
169
|
+
}}>
|
|
170
|
+
Get API key
|
|
171
|
+
</a>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{/* Rival card */}
|
|
175
|
+
<div style={{
|
|
176
|
+
background: 'var(--surface)',
|
|
177
|
+
border: '0.5px solid var(--hairline-2)',
|
|
178
|
+
borderTop: '3px solid var(--hairline-2)',
|
|
179
|
+
borderRadius: 8,
|
|
180
|
+
padding: '20px 22px',
|
|
181
|
+
display: 'flex', flexDirection: 'column', gap: 12,
|
|
182
|
+
}}>
|
|
183
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
184
|
+
<span style={{ fontFamily: 'var(--mono)', fontSize: 16, fontWeight: 600 }}>{rivalCap}</span>
|
|
185
|
+
<span style={{ fontSize: 12, color: TERT }}>Incumbent</span>
|
|
186
|
+
</div>
|
|
187
|
+
<div style={{ fontSize: 14, color: SEC, lineHeight: 1.5 }}>
|
|
188
|
+
A manned terminal: data plus news, messaging, and execution in a single desktop seat.
|
|
189
|
+
</div>
|
|
190
|
+
<div style={{ fontSize: 13, color: TERT }}>
|
|
191
|
+
From <span style={{ color: 'var(--ink)', fontWeight: 500 }}>quote-based</span> · annual seat license
|
|
192
|
+
</div>
|
|
193
|
+
<a href="#" style={{
|
|
194
|
+
marginTop: 'auto',
|
|
195
|
+
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 7,
|
|
196
|
+
fontSize: 14, fontWeight: 500,
|
|
197
|
+
padding: '9px 15px',
|
|
198
|
+
borderRadius: 4,
|
|
199
|
+
border: '0.5px solid var(--hairline-2)',
|
|
200
|
+
background: 'var(--surface)',
|
|
201
|
+
color: 'var(--ink)',
|
|
202
|
+
textDecoration: 'none',
|
|
203
|
+
}}>
|
|
204
|
+
Visit site <ExtIcon />
|
|
205
|
+
</a>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
</div>
|
|
209
|
+
</section>
|
|
210
|
+
|
|
211
|
+
{/* ── Feature table ──────────────────────────────────────────────── */}
|
|
212
|
+
<section style={{ padding: '30px 0', borderBottom: '0.5px solid var(--hairline)' }}>
|
|
213
|
+
<h2 style={{
|
|
214
|
+
fontFamily: 'var(--mono)', fontSize: 20, fontWeight: 600,
|
|
215
|
+
letterSpacing: '-.01em', marginBottom: 16,
|
|
216
|
+
}}>
|
|
217
|
+
Feature by feature
|
|
218
|
+
</h2>
|
|
219
|
+
|
|
220
|
+
<div style={{
|
|
221
|
+
background: 'var(--surface)',
|
|
222
|
+
border: '0.5px solid var(--hairline-2)',
|
|
223
|
+
borderRadius: 8,
|
|
224
|
+
overflow: 'hidden',
|
|
225
|
+
}}>
|
|
226
|
+
{/* Tinted header row */}
|
|
227
|
+
<div style={{
|
|
228
|
+
display: 'grid', gridTemplateColumns: '1.6fr 1fr 1fr',
|
|
229
|
+
alignItems: 'center', gap: 16,
|
|
230
|
+
padding: '11px 20px',
|
|
231
|
+
borderBottom: '0.5px solid var(--hairline-2)',
|
|
232
|
+
background: '#F4F4F1',
|
|
233
|
+
}}>
|
|
234
|
+
<span style={{ fontSize: 10, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '.07em', color: TERT }}>
|
|
235
|
+
Capability
|
|
236
|
+
</span>
|
|
237
|
+
<span style={{ fontSize: 12, fontWeight: 600, color: 'var(--accent)' }}>{brand}</span>
|
|
238
|
+
<span style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink)' }}>{rivalCap}</span>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
{/* Data rows — horizontal hairlines, no zebra */}
|
|
242
|
+
{rows.map((r) => (
|
|
243
|
+
<div key={r.feat} style={{
|
|
244
|
+
display: 'grid', gridTemplateColumns: '1.6fr 1fr 1fr',
|
|
245
|
+
alignItems: 'center', gap: 16,
|
|
246
|
+
padding: '13px 20px',
|
|
247
|
+
borderBottom: '0.5px solid var(--hairline)',
|
|
248
|
+
fontSize: 14,
|
|
249
|
+
}}>
|
|
250
|
+
<span style={{ color: 'var(--ink)' }}>{r.feat}</span>
|
|
251
|
+
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, color: r.aColor }}>{r.aText}</span>
|
|
252
|
+
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, color: r.bColor }}>{r.bText}</span>
|
|
253
|
+
</div>
|
|
254
|
+
))}
|
|
255
|
+
</div>
|
|
256
|
+
</section>
|
|
257
|
+
|
|
258
|
+
{/* ── "Where each wins" prose — stacked vertically (no side-by-side) */}
|
|
259
|
+
<section style={{ padding: '30px 0', borderBottom: '0.5px solid var(--hairline)' }}>
|
|
260
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
261
|
+
{/* Console wins */}
|
|
262
|
+
<div style={{ paddingBottom: 22, borderBottom: '0.5px solid var(--hairline)' }}>
|
|
263
|
+
<h3 style={{
|
|
264
|
+
fontFamily: 'var(--mono)', fontSize: 16, fontWeight: 600,
|
|
265
|
+
letterSpacing: '-.01em', marginBottom: 8,
|
|
266
|
+
}}>
|
|
267
|
+
Where {brand} wins
|
|
268
|
+
</h3>
|
|
269
|
+
<p style={{ fontSize: 16, color: '#3A3D44', lineHeight: 1.65 }}>
|
|
270
|
+
If your data flows into code — backtests, pipelines, dashboards — {brand} fits the way you already work.
|
|
271
|
+
Point-in-time history is default, every value names its source, and you can start free in minutes
|
|
272
|
+
with public, per-call pricing.
|
|
273
|
+
</p>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
{/* Rival leads */}
|
|
277
|
+
<div style={{ paddingTop: 22 }}>
|
|
278
|
+
<h3 style={{
|
|
279
|
+
fontFamily: 'var(--mono)', fontSize: 16, fontWeight: 600,
|
|
280
|
+
letterSpacing: '-.01em', marginBottom: 8,
|
|
281
|
+
}}>
|
|
282
|
+
Where {rival.toLowerCase()} leads
|
|
283
|
+
</h3>
|
|
284
|
+
<p style={{ fontSize: 16, color: '#3A3D44', lineHeight: 1.65 }}>
|
|
285
|
+
If you need a person at a desk with breaking news, dealer chat, and order execution beside the numbers,
|
|
286
|
+
a terminal still does more in one seat. {brand} is data infrastructure, not a trading cockpit.
|
|
287
|
+
</p>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
</section>
|
|
291
|
+
|
|
292
|
+
{/* ── Final verdict ──────────────────────────────────────────────── */}
|
|
293
|
+
<section style={{ padding: '30px 0', borderBottom: '0.5px solid var(--hairline)' }}>
|
|
294
|
+
<div style={{
|
|
295
|
+
background: 'var(--surface)',
|
|
296
|
+
border: '0.5px solid var(--hairline-2)',
|
|
297
|
+
borderRadius: 8,
|
|
298
|
+
padding: '20px 22px',
|
|
299
|
+
}}>
|
|
300
|
+
<div style={{
|
|
301
|
+
fontSize: 11, fontWeight: 600, textTransform: 'uppercase',
|
|
302
|
+
letterSpacing: '.07em', color: TERT, marginBottom: 8,
|
|
303
|
+
}}>
|
|
304
|
+
Verdict
|
|
305
|
+
</div>
|
|
306
|
+
<p style={{ fontSize: 16, color: 'var(--ink)', lineHeight: 1.6 }}>
|
|
307
|
+
Choose <b style={{ fontWeight: 600 }}>{brand}</b> when reproducibility, automation, and cost matter
|
|
308
|
+
more than a manned interface. Keep <b style={{ fontWeight: 600 }}>{rival.toLowerCase()}</b> for the desk that needs
|
|
309
|
+
news, chat, and execution together. Many teams run both — {brand} feeds the models, the terminal
|
|
310
|
+
sits with the trader.
|
|
311
|
+
</p>
|
|
312
|
+
</div>
|
|
313
|
+
</section>
|
|
314
|
+
|
|
315
|
+
{/* ── Related comparisons ────────────────────────────────────────── */}
|
|
316
|
+
<section style={{ padding: '30px 0 8px' }}>
|
|
317
|
+
<h2 style={{
|
|
318
|
+
fontFamily: 'var(--mono)', fontSize: 20, fontWeight: 600,
|
|
319
|
+
letterSpacing: '-.01em', marginBottom: 16,
|
|
320
|
+
}}>
|
|
321
|
+
Related comparisons
|
|
322
|
+
</h2>
|
|
323
|
+
|
|
324
|
+
<div style={{
|
|
325
|
+
display: 'grid',
|
|
326
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))',
|
|
327
|
+
gap: 14,
|
|
328
|
+
}}>
|
|
329
|
+
{related.map((card) => (
|
|
330
|
+
<a key={card.title} href="#" style={{
|
|
331
|
+
display: 'flex', flexDirection: 'column', gap: 8,
|
|
332
|
+
background: 'var(--surface)',
|
|
333
|
+
border: '0.5px solid var(--hairline-2)',
|
|
334
|
+
borderRadius: 8,
|
|
335
|
+
padding: '18px 20px',
|
|
336
|
+
color: 'inherit',
|
|
337
|
+
textDecoration: 'none',
|
|
338
|
+
minHeight: 104,
|
|
339
|
+
}}>
|
|
340
|
+
<span style={{
|
|
341
|
+
fontSize: 11, fontWeight: 600, textTransform: 'uppercase',
|
|
342
|
+
letterSpacing: '.07em', color: 'var(--accent)',
|
|
343
|
+
}}>
|
|
344
|
+
{card.type}
|
|
345
|
+
</span>
|
|
346
|
+
<span style={{ fontSize: 15, fontWeight: 500, color: 'var(--ink)', lineHeight: 1.4 }}>
|
|
347
|
+
{card.title}
|
|
348
|
+
</span>
|
|
349
|
+
<span style={{
|
|
350
|
+
marginTop: 'auto',
|
|
351
|
+
display: 'inline-flex', alignItems: 'center', gap: 6,
|
|
352
|
+
fontSize: 13, fontWeight: 500, color: 'var(--accent)',
|
|
353
|
+
}}>
|
|
354
|
+
Read <ArrowRight />
|
|
355
|
+
</span>
|
|
356
|
+
</a>
|
|
357
|
+
))}
|
|
358
|
+
</div>
|
|
359
|
+
</section>
|
|
360
|
+
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
);
|
|
364
|
+
}
|