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.
Files changed (34) hide show
  1. package/README.md +36 -0
  2. package/package.json +40 -0
  3. package/ui/ThemeProvider.tsx +16 -0
  4. package/ui/components/ArticlePage.tsx +673 -0
  5. package/ui/components/AuthPage.tsx +109 -0
  6. package/ui/components/Callout.tsx +79 -0
  7. package/ui/components/Chart.tsx +100 -0
  8. package/ui/components/CodeTabs.tsx +145 -0
  9. package/ui/components/ComparePage.tsx +364 -0
  10. package/ui/components/DashboardPage.tsx +773 -0
  11. package/ui/components/DocsNav.tsx +80 -0
  12. package/ui/components/Footer.tsx +136 -0
  13. package/ui/components/HubPage.tsx +529 -0
  14. package/ui/components/Modal.tsx +412 -0
  15. package/ui/components/Nav.tsx +162 -0
  16. package/ui/components/OnThisPage.tsx +56 -0
  17. package/ui/components/ParamsTable.tsx +86 -0
  18. package/ui/components/RangeBar.tsx +68 -0
  19. package/ui/components/SeriesChart.tsx +218 -0
  20. package/ui/components/SeriesPage.tsx +461 -0
  21. package/ui/components/StatusMark.tsx +48 -0
  22. package/ui/components/publictrades/ExternalLink.tsx +37 -0
  23. package/ui/components/publictrades/FactsCard.tsx +40 -0
  24. package/ui/components/publictrades/LedgerFooter.tsx +22 -0
  25. package/ui/components/publictrades/LedgerNav.tsx +78 -0
  26. package/ui/components/publictrades/Mark.tsx +40 -0
  27. package/ui/components/publictrades/SourceBlock.tsx +91 -0
  28. package/ui/components/publictrades/Tape.tsx +164 -0
  29. package/ui/components/publictrades/ThreeStamps.tsx +47 -0
  30. package/ui/components/publictrades/types.ts +19 -0
  31. package/ui/data/series.ts +851 -0
  32. package/ui/index.ts +50 -0
  33. package/ui/publictrades.ts +16 -0
  34. 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
+ }