instadash-embed 0.1.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 (2) hide show
  1. package/embed.html +298 -0
  2. package/package.json +20 -0
package/embed.html ADDED
@@ -0,0 +1,298 @@
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>Instadash</title>
7
+ <style>
8
+ *,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
9
+
10
+ :root{
11
+ --bg:#1B1F24;--bg-1:#1F242A;--bg-2:#252B32;
12
+ --fg:#E8EAED;--fg-2:#9CA3AF;--fg-3:#6B7280;
13
+ --line:rgba(255,255,255,.07);
14
+ --accent:#00E5FF;--lime:#39D353;--warn:#F5A623;
15
+ --mono:'SF Mono','Fira Code','Consolas',monospace;
16
+ }
17
+
18
+ html,body{height:100%;overflow:hidden}
19
+ body{background:var(--bg);color:var(--fg);font-family:system-ui,-apple-system,sans-serif;font-size:13px;display:flex;flex-direction:column}
20
+
21
+ /* ── Header ─────────────────────────────────────────────────── */
22
+ .bar{
23
+ display:flex;align-items:center;gap:8px;
24
+ padding:7px 12px;
25
+ background:var(--bg-1);
26
+ border-bottom:1px solid var(--line);
27
+ flex-shrink:0;
28
+ }
29
+ .mark{
30
+ width:18px;height:18px;
31
+ display:flex;align-items:center;justify-content:center;
32
+ border:1px solid var(--accent);border-radius:3px;
33
+ color:var(--accent);font-family:var(--mono);font-size:10px;font-weight:700;
34
+ flex-shrink:0;
35
+ }
36
+ .wordmark{font-size:12px;font-weight:700;letter-spacing:-.025em;color:var(--fg);flex-shrink:0}
37
+ .wordmark span{color:var(--accent)}
38
+ .grid-title{
39
+ font-size:12px;color:var(--fg-2);
40
+ white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
41
+ flex:1;min-width:0;padding-left:4px;
42
+ font-family:var(--mono);
43
+ }
44
+ .open-link{
45
+ color:var(--accent);font-size:11px;font-family:var(--mono);
46
+ text-decoration:none;flex-shrink:0;white-space:nowrap;
47
+ }
48
+ .open-link:hover{text-decoration:underline}
49
+
50
+ /* ── Status bar ─────────────────────────────────────────────── */
51
+ .statusbar{
52
+ display:flex;align-items:center;
53
+ padding:3px 12px;
54
+ border-bottom:1px solid var(--line);
55
+ flex-shrink:0;
56
+ }
57
+ .rowcount{font-size:10px;color:var(--fg-3);font-family:var(--mono)}
58
+
59
+ /* ── Table ──────────────────────────────────────────────────── */
60
+ .wrap{flex:1;overflow:auto}
61
+
62
+ table{width:100%;border-collapse:collapse;font-family:var(--mono);font-size:12px}
63
+
64
+ thead th{
65
+ background:var(--bg-1);
66
+ padding:7px 12px;
67
+ text-align:left;font-size:11px;font-weight:600;color:var(--fg-2);
68
+ border-bottom:1px solid var(--line);
69
+ position:sticky;top:0;z-index:1;
70
+ white-space:nowrap;cursor:pointer;user-select:none;
71
+ }
72
+ thead th:hover{color:var(--fg)}
73
+ thead th.sorted{color:var(--accent)}
74
+ thead th.num{text-align:right}
75
+
76
+ tbody td{
77
+ padding:6px 12px;
78
+ border-bottom:1px solid rgba(255,255,255,.03);
79
+ max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
80
+ }
81
+ tbody tr:nth-child(even) td{background:var(--bg-1)}
82
+ tbody tr:hover td{background:var(--bg-2)}
83
+
84
+ td.num{text-align:right;color:var(--warn)}
85
+ td.url a{color:var(--accent);text-decoration:none}
86
+ td.url a:hover{text-decoration:underline}
87
+ td.bool .t{color:var(--lime)}
88
+ td.bool .f{color:var(--fg-3)}
89
+
90
+ /* ── Chips ──────────────────────────────────────────────────── */
91
+ .chip{
92
+ display:inline-block;
93
+ padding:1px 6px;border-radius:3px;
94
+ font-size:10px;border:1px solid;white-space:nowrap;
95
+ }
96
+ .c0{color:#00E5FF;border-color:rgba(0,229,255,.3);background:rgba(0,229,255,.06)}
97
+ .c1{color:#39D353;border-color:rgba(57,211,83,.3);background:rgba(57,211,83,.06)}
98
+ .c2{color:#F5A623;border-color:rgba(245,166,35,.3);background:rgba(245,166,35,.06)}
99
+ .c3{color:#BF5AF2;border-color:rgba(191,90,242,.3);background:rgba(191,90,242,.06)}
100
+ .c4{color:#FF6B6B;border-color:rgba(255,107,107,.3);background:rgba(255,107,107,.06)}
101
+ .c5{color:#9CA3AF;border-color:rgba(156,163,175,.3);background:rgba(156,163,175,.06)}
102
+
103
+ /* ── States ─────────────────────────────────────────────────── */
104
+ .state{
105
+ position:fixed;inset:0;display:flex;align-items:center;justify-content:center;
106
+ flex-direction:column;gap:8px;
107
+ background:var(--bg);color:var(--fg-3);font-family:var(--mono);font-size:12px;
108
+ }
109
+ .state .err{color:#FF6B6B}
110
+ .state a{color:var(--accent);margin-top:8px}
111
+ </style>
112
+ </head>
113
+ <body>
114
+
115
+ <div class="bar">
116
+ <div class="mark">›</div>
117
+ <div class="wordmark">insta<span>dash</span></div>
118
+ <div class="grid-title" id="grid-title"></div>
119
+ <a class="open-link" id="open-link" href="#" target="_blank" rel="noopener">open grid ↗</a>
120
+ </div>
121
+
122
+ <div id="root"><div class="state">Loading…</div></div>
123
+
124
+ <script>
125
+ (function () {
126
+ var p = new URLSearchParams(location.search)
127
+ var handle = p.get('handle') || ''
128
+ var slug = p.get('slug') || ''
129
+ var base = 'https://instadash.io'
130
+ var gridUrl = base + '/' + handle + '/' + slug
131
+
132
+ document.getElementById('open-link').href = gridUrl
133
+
134
+ if (!handle || !slug) {
135
+ setRoot('<div class="state"><span class="err">Missing ?handle= and ?slug= params</span></div>')
136
+ return
137
+ }
138
+
139
+ // ── Type detection ──────────────────────────────────────────
140
+ function detectType(values) {
141
+ var nonNull = values.filter(function(v){ return v !== null && v !== undefined && v !== '' })
142
+ if (!nonNull.length) return 'string'
143
+ if (nonNull.every(function(v){ return v === true || v === false || v === 'true' || v === 'false' })) return 'bool'
144
+ if (nonNull.every(function(v){
145
+ var s = String(v).replace(/[$,%]/g, '')
146
+ return !isNaN(parseFloat(s)) && isFinite(s)
147
+ })) return 'num'
148
+ if (nonNull.every(function(v){ return typeof v === 'string' && /^https?:\/\//.test(v) })) return 'url'
149
+ var distinct = unique(nonNull.map(function(v){ return String(v) }))
150
+ if (distinct.length <= 10 && distinct.every(function(v){ return v.length <= 28 })) return 'chip'
151
+ return 'string'
152
+ }
153
+
154
+ function unique(arr) {
155
+ return arr.filter(function(v, i, a){ return a.indexOf(v) === i })
156
+ }
157
+
158
+ // Per-column chip color maps
159
+ var colMaps = {}
160
+ function chipCls(col, val) {
161
+ if (!colMaps[col]) colMaps[col] = { m: {}, n: 0 }
162
+ var cm = colMaps[col], k = String(val).toLowerCase()
163
+ if (!(k in cm.m)) cm.m[k] = cm.n++ % 6
164
+ return 'chip c' + cm.m[k]
165
+ }
166
+
167
+ // ── Sort state ──────────────────────────────────────────────
168
+ var sortCol = null, sortDir = 'asc'
169
+
170
+ function esc(s) {
171
+ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')
172
+ }
173
+
174
+ function renderTable(rows, meta) {
175
+ if (!rows.length) {
176
+ setRoot('<div class="state">No rows found</div>')
177
+ return
178
+ }
179
+
180
+ var cols = Object.keys(rows[0])
181
+ var types = {}
182
+ cols.forEach(function(col) {
183
+ types[col] = detectType(rows.map(function(r){ return r[col] }))
184
+ })
185
+
186
+ // Pre-seed chip colors for stable ordering
187
+ colMaps = {}
188
+ cols.forEach(function(col) {
189
+ if (types[col] === 'chip') {
190
+ unique(rows.map(function(r){ return String(r[col] ?? '') })).forEach(function(v){ chipCls(col, v) })
191
+ }
192
+ })
193
+
194
+ // Sort
195
+ var sorted = rows.slice()
196
+ if (sortCol && cols.indexOf(sortCol) >= 0) {
197
+ var t = types[sortCol]
198
+ sorted.sort(function(a, b) {
199
+ var va = a[sortCol] ?? '', vb = b[sortCol] ?? ''
200
+ if (t === 'num') {
201
+ va = parseFloat(String(va).replace(/[$,%]/g, '')) || 0
202
+ vb = parseFloat(String(vb).replace(/[$,%]/g, '')) || 0
203
+ } else {
204
+ va = String(va).toLowerCase()
205
+ vb = String(vb).toLowerCase()
206
+ }
207
+ return va < vb ? (sortDir === 'asc' ? -1 : 1) : va > vb ? (sortDir === 'asc' ? 1 : -1) : 0
208
+ })
209
+ }
210
+
211
+ // Header
212
+ var ths = cols.map(function(col) {
213
+ var isSorted = sortCol === col
214
+ var cls = (types[col] === 'num' ? ' num' : '') + (isSorted ? ' sorted' : '')
215
+ var icon = isSorted ? (sortDir === 'asc' ? ' ↑' : ' ↓') : ''
216
+ return '<th class="' + cls.trim() + '" data-col="' + esc(col) + '">' + esc(col) + '<span style="opacity:.5;margin-left:3px">' + (icon || ' ↕') + '</span></th>'
217
+ }).join('')
218
+
219
+ // Rows
220
+ var trs = sorted.map(function(row) {
221
+ var tds = cols.map(function(col) {
222
+ var v = row[col]
223
+ var t = types[col]
224
+ var s = v === null || v === undefined ? '' : String(v)
225
+ if (t === 'url' && s) {
226
+ return '<td class="url"><a href="' + esc(s) + '" target="_blank" rel="noopener">' + esc(s) + '</a></td>'
227
+ }
228
+ if (t === 'bool') {
229
+ var isTrue = v === true || v === 'true' || v === 1 || v === '1'
230
+ return '<td class="bool"><span class="' + (isTrue ? 't' : 'f') + '">' + (isTrue ? '✓' : '✗') + '</span></td>'
231
+ }
232
+ if (t === 'num') {
233
+ return '<td class="num">' + esc(s) + '</td>'
234
+ }
235
+ if (t === 'chip' && s) {
236
+ return '<td><span class="' + chipCls(col, s) + '">' + esc(s) + '</span></td>'
237
+ }
238
+ return '<td title="' + esc(s) + '">' + esc(s) + '</td>'
239
+ }).join('')
240
+ return '<tr>' + tds + '</tr>'
241
+ }).join('')
242
+
243
+ var title = meta ? (meta.title || (handle + '/' + slug)) : (handle + '/' + slug)
244
+ var total = meta ? meta.rows : rows.length
245
+ document.getElementById('grid-title').textContent = title
246
+
247
+ setRoot(
248
+ '<div class="statusbar"><span class="rowcount">' +
249
+ sorted.length + (sorted.length < total ? ' of ' + total : '') + ' rows · ' +
250
+ cols.length + ' columns · click header to sort' +
251
+ '</span></div>' +
252
+ '<div class="wrap"><table>' +
253
+ '<thead><tr>' + ths + '</tr></thead>' +
254
+ '<tbody>' + trs + '</tbody>' +
255
+ '</table></div>'
256
+ )
257
+
258
+ document.querySelectorAll('thead th').forEach(function(th) {
259
+ th.addEventListener('click', function() {
260
+ var col = th.dataset.col
261
+ if (sortCol === col) {
262
+ sortDir = sortDir === 'asc' ? 'desc' : 'asc'
263
+ } else {
264
+ sortCol = col; sortDir = 'asc'
265
+ }
266
+ renderTable(rows, meta)
267
+ })
268
+ })
269
+ }
270
+
271
+ function setRoot(html) {
272
+ document.getElementById('root').innerHTML = html
273
+ }
274
+
275
+ // ── Fetch ───────────────────────────────────────────────────
276
+ Promise.all([
277
+ fetch(gridUrl + '/rows?limit=2000').then(function(r) {
278
+ return r.ok ? r.json() : Promise.reject('HTTP ' + r.status)
279
+ }),
280
+ fetch(gridUrl + '/meta').then(function(r) {
281
+ return r.ok ? r.json() : null
282
+ }).catch(function() { return null }),
283
+ ])
284
+ .then(function(res) { renderTable(res[0].rows || [], res[1]) })
285
+ .catch(function(e) {
286
+ setRoot(
287
+ '<div class="state">' +
288
+ '<span class="err">Could not load grid</span>' +
289
+ '<span>' + esc(handle + '/' + slug) + ' · ' + esc(String(e)) + '</span>' +
290
+ '<a href="' + esc(gridUrl) + '" target="_blank" rel="noopener">open in instadash ↗</a>' +
291
+ '</div>'
292
+ )
293
+ })
294
+ })()
295
+ </script>
296
+
297
+ </body>
298
+ </html>
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "instadash-embed",
3
+ "version": "0.1.0",
4
+ "description": "Self-contained grid embed widget for instadash.io — works in Claude.ai artifacts via cdn.jsdelivr.net",
5
+ "license": "MIT",
6
+ "files": [
7
+ "embed.html"
8
+ ],
9
+ "keywords": [
10
+ "instadash",
11
+ "embed",
12
+ "grid",
13
+ "data",
14
+ "mcp"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/instadash-io/embed"
19
+ }
20
+ }