@digital-ai/dot-illustrations 2.0.36 → 2.0.38

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.
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * local-proxy.js — GitHub API proxy for local demo development
4
+ *
5
+ * Uses your existing `gh auth` credentials — no token input needed.
6
+ * Run from the repo root: node demo/local-proxy.js
7
+ *
8
+ * The demo page detects this proxy at http://localhost:3001 and
9
+ * authenticates automatically using your gh CLI identity.
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const http = require('http');
15
+ const https = require('https');
16
+ const { execSync } = require('child_process');
17
+
18
+ const PROXY_PORT = 3001;
19
+
20
+ // Resolve the gh CLI token once at startup
21
+ let GH_TOKEN, GH_USER;
22
+ try {
23
+ GH_TOKEN = execSync('gh auth token', { stdio: ['pipe','pipe','pipe'] }).toString().trim();
24
+ GH_USER = execSync('gh api user --jq .login', { stdio: ['pipe','pipe','pipe'] }).toString().trim();
25
+ } catch (e) {
26
+ console.error('❌ gh CLI is not authenticated. Run: gh auth login --web');
27
+ process.exit(1);
28
+ }
29
+
30
+ const server = http.createServer((req, res) => {
31
+ // Allow the demo page (any localhost origin) to call this proxy
32
+ res.setHeader('Access-Control-Allow-Origin', '*');
33
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
34
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
35
+
36
+ if (req.method === 'OPTIONS') {
37
+ res.writeHead(204);
38
+ res.end();
39
+ return;
40
+ }
41
+
42
+ // Special: /whoami — tells the demo page who is logged in
43
+ if (req.url === '/whoami') {
44
+ res.writeHead(200, { 'Content-Type': 'application/json' });
45
+ res.end(JSON.stringify({ login: GH_USER, proxy: true }));
46
+ return;
47
+ }
48
+
49
+ // Proxy everything else to api.github.com
50
+ let body = '';
51
+ req.on('data', chunk => { body += chunk; });
52
+ req.on('end', () => {
53
+ const options = {
54
+ hostname: 'api.github.com',
55
+ path: req.url,
56
+ method: req.method,
57
+ headers: {
58
+ 'Authorization': `Bearer ${GH_TOKEN}`,
59
+ 'Accept': 'application/vnd.github.v3+json',
60
+ 'Content-Type': 'application/json',
61
+ 'User-Agent': 'dot-illustrations-local-proxy',
62
+ ...(body ? { 'Content-Length': Buffer.byteLength(body) } : {}),
63
+ },
64
+ };
65
+
66
+ const proxyReq = https.request(options, proxyRes => {
67
+ res.writeHead(proxyRes.statusCode, {
68
+ 'Content-Type': proxyRes.headers['content-type'] || 'application/json',
69
+ 'Access-Control-Allow-Origin': '*',
70
+ });
71
+ proxyRes.pipe(res);
72
+ });
73
+
74
+ proxyReq.on('error', err => {
75
+ res.writeHead(502);
76
+ res.end(JSON.stringify({ error: err.message }));
77
+ });
78
+
79
+ if (body) proxyReq.write(body);
80
+ proxyReq.end();
81
+ });
82
+ });
83
+
84
+ server.listen(PROXY_PORT, '127.0.0.1', () => {
85
+ console.log(`✅ GitHub proxy running at http://localhost:${PROXY_PORT}`);
86
+ console.log(`👤 Authenticated as: @${GH_USER}`);
87
+ console.log(`\n Open the demo: http://localhost:5175/demo/`);
88
+ console.log(' The demo will connect automatically — no token input needed.\n');
89
+ console.log(' Press Ctrl+C to stop.\n');
90
+ });
package/demo/script.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // Make currentTab and root globally accessible
2
2
  const root = document.documentElement;
3
- let currentTab = 'all';
4
- let alphabetFilter = ''; // '' = show all letters
3
+ let currentSection = 'illustrations'; // 'illustrations' | 'integrations' | 'upload'
4
+ let currentTab = 'all';
5
+ let alphabetFilter = '';
5
6
 
6
7
  function myFunction() {
7
8
  var input, filter, divs, i, div, txtValue;
@@ -116,48 +117,117 @@ document.addEventListener('DOMContentLoaded', () => {
116
117
  const mainTabs = document.querySelectorAll('.tab-btn');
117
118
  const floatingTabs = document.querySelectorAll('.floating-tab');
118
119
 
119
- function updateTabUI(tab) {
120
- [...mainTabs, ...floatingTabs].forEach(t => {
121
- const isActive = t.dataset.tab === tab;
122
- t.classList.toggle('text-blue-600', isActive);
123
- t.classList.toggle('dark:text-blue-400', isActive);
124
- t.classList.toggle('bg-blue-100', isActive);
125
- t.classList.toggle('dark:bg-blue-900', isActive);
126
- t.classList.toggle('text-gray-700', !isActive && t.dataset.tab !== 'upload');
127
- t.classList.toggle('dark:text-gray-300', !isActive && t.dataset.tab !== 'upload');
128
- t.setAttribute('aria-selected', isActive ? 'true' : 'false');
120
+ // ── Upload New button (in header) ────────────────────────────────────────
121
+ // ── Upload New: type picker → form ──────────────────────────────────────
122
+ function showUploadStep(step) {
123
+ // step: 'picker' | 'illustration' | 'integration'
124
+ document.getElementById('upload-type-picker').classList.toggle('hidden', step !== 'picker');
125
+ document.getElementById('upload-illus-panel').classList.toggle('hidden', step !== 'illustration');
126
+ document.getElementById('upload-integ-panel').classList.toggle('hidden', step !== 'integration');
127
+ }
128
+
129
+ function openUploadModal() {
130
+ const panel = document.getElementById('upload-panel');
131
+ panel.classList.remove('hidden');
132
+ showUploadStep('picker');
133
+ updateUploadAuthUI();
134
+ document.getElementById('upload-new-btn').classList.add('ring-2','ring-blue-400','bg-blue-700');
135
+ }
136
+ function closeUploadModal() {
137
+ document.getElementById('upload-panel').classList.add('hidden');
138
+ document.getElementById('upload-new-btn').classList.remove('ring-2','ring-blue-400','bg-blue-700');
139
+ }
140
+
141
+ document.getElementById('upload-new-btn')?.addEventListener('click', openUploadModal);
142
+
143
+ // Backdrop click does NOT close — use X button or Escape
144
+
145
+ // Close on X button
146
+ document.getElementById('upload-modal-close')?.addEventListener('click', closeUploadModal);
147
+
148
+ // Close on Escape
149
+ document.addEventListener('keydown', e => {
150
+ if (e.key === 'Escape' && !document.getElementById('upload-panel').classList.contains('hidden')) closeUploadModal();
151
+ });
152
+
153
+ document.getElementById('pick-illustration')?.addEventListener('click', () => { showUploadStep('illustration'); updateUploadAuthUI(); });
154
+ document.getElementById('pick-integration')?.addEventListener('click', () => { showUploadStep('integration'); updateUploadAuthUI(); });
155
+ document.getElementById('back-from-illus')?.addEventListener('click', () => showUploadStep('picker'));
156
+ document.getElementById('back-from-integ')?.addEventListener('click', () => showUploadStep('picker'));
157
+
158
+ // ── Section switcher (Illustrations | Integrations) — pill style ──────────
159
+ function updateSectionUI(section) {
160
+ document.querySelectorAll('.section-btn, .floating-section-btn').forEach(btn => {
161
+ const active = btn.dataset.section === section;
162
+ // Active pill: white card on gray background
163
+ btn.classList.toggle('bg-white', active);
164
+ btn.classList.toggle('dark:bg-gray-700', active);
165
+ btn.classList.toggle('text-gray-900', active);
166
+ btn.classList.toggle('dark:text-white', active);
167
+ btn.classList.toggle('shadow-sm', active);
168
+ // Inactive: transparent
169
+ btn.classList.toggle('text-gray-500', !active);
170
+ btn.classList.toggle('dark:text-gray-400', !active);
129
171
  });
130
172
  }
131
173
 
132
- function setTab(tab) {
133
- currentTab = tab;
174
+ function setSection(section) {
175
+ currentSection = section;
134
176
  alphabetFilter = '';
135
- updateTabUI(tab);
177
+ updateSectionUI(section);
136
178
 
137
- const uploadPanel = document.getElementById('upload-panel');
138
- const grid = document.getElementById('illustration-list');
139
- const alphaStrip = document.getElementById('alphabet-strip');
140
- const integNote = document.getElementById('integrations-note');
141
-
142
- if (tab === 'upload') {
143
- uploadPanel.classList.remove('hidden');
144
- grid.classList.add('hidden');
145
- alphaStrip.classList.add('hidden');
146
- integNote.classList.add('hidden');
147
- document.getElementById('result-count').textContent = '';
148
- updateUploadAuthUI();
149
- } else {
150
- uploadPanel.classList.add('hidden');
179
+ const grid = document.getElementById('illustration-list');
180
+ const alphaStrip = document.getElementById('alphabet-strip');
181
+ const illusSubTabs = document.getElementById('illus-sub-tabs');
182
+ const floatingTabs = document.getElementById('floating-tabs');
183
+ const resultCount = document.getElementById('result-count');
184
+
185
+ if (section === 'integrations') {
151
186
  grid.classList.remove('hidden');
152
- // Show alphabet strip only for tabs that have many items
153
- const showStrip = ['all','global','dashboards','integrations','favourites'].includes(tab);
154
- alphaStrip.classList.toggle('hidden', !showStrip);
155
- integNote.classList.toggle('hidden', tab !== 'integrations');
187
+ illusSubTabs.classList.add('hidden');
188
+ if (floatingTabs) floatingTabs.classList.add('hidden');
189
+ // Alphabet strip — show as flex for integrations (110 items benefit from A–Z jump)
190
+ alphaStrip.style.display = 'flex';
156
191
  renderAlphabetStrip();
157
192
  renderIllustrations();
193
+ } else {
194
+ // Illustrations — search is sufficient for 72 items; hide alphabet strip
195
+ grid.classList.remove('hidden');
196
+ illusSubTabs.classList.remove('hidden');
197
+ if (floatingTabs) floatingTabs.classList.remove('hidden');
198
+ alphaStrip.style.display = 'none';
199
+ alphabetFilter = '';
200
+ renderIllustrations();
158
201
  }
159
202
  }
160
203
 
204
+ document.querySelectorAll('.section-btn, .floating-section-btn').forEach(btn => {
205
+ btn.addEventListener('click', () => setSection(btn.dataset.section));
206
+ });
207
+
208
+ // ── Illustration sub-tabs — pill style ───────────────────────────────────
209
+ function updateTabUI(tab) {
210
+ document.querySelectorAll('.tab-btn').forEach(t => {
211
+ const active = t.dataset.tab === tab;
212
+ t.classList.toggle('bg-white', active);
213
+ t.classList.toggle('dark:bg-gray-700', active);
214
+ t.classList.toggle('text-gray-900', active);
215
+ t.classList.toggle('dark:text-white', active);
216
+ t.classList.toggle('shadow-sm', active);
217
+ t.classList.toggle('text-gray-500', !active);
218
+ t.classList.toggle('dark:text-gray-400', !active);
219
+ t.setAttribute('aria-selected', active ? 'true' : 'false');
220
+ });
221
+ }
222
+
223
+ function setTab(tab) {
224
+ currentTab = tab;
225
+ alphabetFilter = '';
226
+ updateTabUI(tab);
227
+ renderAlphabetStrip();
228
+ renderIllustrations();
229
+ }
230
+
161
231
  function wireTabs() {
162
232
  document.querySelectorAll('.tab-btn').forEach(tab => {
163
233
  tab.addEventListener('click', function() { setTab(this.dataset.tab); });
@@ -311,7 +381,7 @@ document.addEventListener('DOMContentLoaded', () => {
311
381
 
312
382
  let imgHtml, dotCodeString, codeString;
313
383
  if (isInteg) {
314
- imgHtml = `<span class="dot-integration" style="display:inline-flex;align-items:center;justify-content:center;width:200px;height:200px;"><img class="${item}" style="width:100%;height:100%;object-fit:contain;"/></span>`;
384
+ imgHtml = `<img src="../integrations/${item}.svg" alt="${item}" style="width:160px;height:160px;object-fit:contain;" />`;
315
385
  dotCodeString = `<span class="dot-integration"><img class="${item}"/></span>`;
316
386
  codeString = dotCodeString;
317
387
  } else {
@@ -368,14 +438,19 @@ document.addEventListener('DOMContentLoaded', () => {
368
438
  const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
369
439
 
370
440
  strip.innerHTML = `
371
- <button class="alpha-btn px-2 py-0.5 text-xs rounded font-mono ${alphabetFilter === '' ? 'bg-blue-600 text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600'} transition" data-letter="">All</button>
441
+ <button class="alpha-btn text-xs font-medium px-1.5 py-0.5 rounded-md transition-all ${
442
+ alphabetFilter === ''
443
+ ? 'text-blue-600 dark:text-blue-400 font-semibold'
444
+ : 'text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
445
+ }" data-letter="">All</button>
446
+ <span class="text-gray-200 dark:text-gray-700 text-xs select-none">|</span>
372
447
  ${letters.map(l => `
373
- <button class="alpha-btn px-2 py-0.5 text-xs rounded font-mono ${
448
+ <button class="alpha-btn text-xs font-medium px-1.5 py-0.5 rounded-md transition-all ${
374
449
  available.has(l)
375
450
  ? alphabetFilter === l
376
- ? 'bg-blue-600 text-white'
377
- : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-blue-100 dark:hover:bg-blue-800 transition'
378
- : 'opacity-30 cursor-default bg-gray-100 dark:bg-gray-800 text-gray-400'
451
+ ? 'text-blue-600 dark:text-blue-400 font-semibold bg-blue-50 dark:bg-blue-900/30'
452
+ : 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-800'
453
+ : 'text-gray-200 dark:text-gray-700 cursor-default'
379
454
  }" data-letter="${l}" ${available.has(l) ? '' : 'disabled'}>${l}</button>
380
455
  `).join('')}
381
456
  `;
@@ -391,11 +466,15 @@ document.addEventListener('DOMContentLoaded', () => {
391
466
  function getCurrentPool(filterOverride) {
392
467
  const f = filterOverride !== undefined ? filterOverride : alphabetFilter;
393
468
  let base = [];
394
- if (currentTab === 'all') base = illustrations;
395
- else if (currentTab === 'global') base = globalList;
396
- else if (currentTab === 'dashboards') base = dashboardsList;
397
- else if (currentTab === 'integrations') base = integrationsList;
398
- else if (currentTab === 'favourites') base = [...illustrations, ...integrationsList].filter(id => getFavourites().includes(id));
469
+ if (currentSection === 'integrations') {
470
+ base = integrationsList;
471
+ } else {
472
+ // illustrations section
473
+ if (currentTab === 'all') base = illustrations;
474
+ else if (currentTab === 'global') base = globalList;
475
+ else if (currentTab === 'dashboards') base = dashboardsList;
476
+ else if (currentTab === 'favourites') base = illustrations.filter(id => getFavourites().includes(id));
477
+ }
399
478
  if (f) base = base.filter(id => id[0].toUpperCase() === f);
400
479
  return base;
401
480
  }
@@ -417,8 +496,7 @@ document.addEventListener('DOMContentLoaded', () => {
417
496
  return;
418
497
  }
419
498
 
420
- const isIntegrations = currentTab === 'integrations' ||
421
- (currentTab === 'favourites' && toShow.every(id => integrationsList.includes(id)));
499
+ const isIntegrations = currentSection === 'integrations';
422
500
  const favs = getFavourites();
423
501
  const themeClass = root.classList.contains('dark') ? 'dark' : 'light';
424
502
 
@@ -427,57 +505,54 @@ document.addEventListener('DOMContentLoaded', () => {
427
505
  const isFav = favs.includes(item);
428
506
 
429
507
  if (isInteg) {
430
- // Integration card (single SVG, dot-integration wrapper)
431
- const codeString = `<span class="dot-integration"><img class="${item}"/></span>`;
508
+ // Integration card use direct <img src> so the image always loads
509
+ // ── Integration card ─────────────────────────────────────────────
510
+ const htmlCode = `<span class="dot-integration"><img class="${item}"/></span>`;
432
511
  const div = document.createElement('div');
433
- div.setAttribute('class', 'copy-container relative group flex flex-col items-center justify-between bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl shadow-lg p-2 md:p-3 transition-transform duration-200 hover:scale-105 hover:shadow-2xl h-[302px] w-full');
434
- div.dataset.itemId = item;
512
+ div.className = 'copy-container group relative bg-white dark:bg-gray-900 rounded-2xl overflow-hidden cursor-pointer shadow-sm transition-all duration-200 hover:shadow-md hover:ring-2 hover:ring-blue-400/30 dark:hover:ring-blue-500/30';
513
+ div.dataset.itemId = item;
435
514
  div.dataset.itemType = 'integration';
436
515
  div.innerHTML = `
437
- <button class="absolute top-2 right-2 z-10 p-1 rounded-full bg-white/80 dark:bg-gray-800/80 hover:bg-yellow-100 dark:hover:bg-yellow-300 transition" title="Toggle favourite" onclick="event.stopPropagation();window.toggleFavourite('${item}')">
438
- <svg class="h-5 w-5" fill="${isFav ? 'gold' : 'none'}" viewBox="0 0 24 24" stroke="gold"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l2.036 6.29a1 1 0 00.95.69h6.631c.969 0 1.371 1.24.588 1.81l-5.37 3.905a1 1 0 00-.364 1.118l2.036 6.29c.3.921-.755 1.688-1.54 1.118l-5.37-3.905a1 1 0 00-1.176 0l-5.37 3.905c-.784.57-1.838-.197-1.54-1.118l2.036-6.29a1 1 0 00-.364-1.118L2.342 11.717c-.783-.57-.38-1.81.588-1.81h6.631a1 1 0 00.95-.69l2.036-6.29z"/></svg>
516
+ <button class="fav-btn absolute top-2.5 right-2.5 z-20 opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded-lg bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm shadow-sm" onclick="event.stopPropagation();window.toggleFavourite('${item}')">
517
+ <svg class="w-3.5 h-3.5" fill="${isFav ? '#FBBF24' : 'none'}" viewBox="0 0 24 24" stroke="${isFav ? '#FBBF24' : '#9CA3AF'}" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l2.036 6.29a1 1 0 00.95.69h6.631c.969 0 1.371 1.24.588 1.81l-5.37 3.905a1 1 0 00-.364 1.118l2.036 6.29c.3.921-.755 1.688-1.54 1.118l-5.37-3.905a1 1 0 00-1.176 0l-5.37 3.905c-.784.57-1.838-.197-1.54-1.118l2.036-6.29a1 1 0 00-.364-1.118L2.342 11.717c-.783-.57-.38-1.81.588-1.81h6.631a1 1 0 00.95-.69l2.036-6.29z"/></svg>
439
518
  </button>
440
- <div class="flex-1 flex flex-col justify-center items-center">
441
- <span class="dot-integration flex items-center justify-center" style="width:160px;height:160px;"><img class="${item}" style="width:100%;height:100%;object-fit:contain;"/></span>
442
- <span class="iconID block text-center text-base font-medium text-gray-800 dark:text-gray-200 my-2">${item}</span>
519
+ <div class="flex items-center justify-center h-36 px-4">
520
+ <img src="../integrations/${item}.svg" alt="${item}" class="w-[80px] h-[80px] object-contain" loading="lazy" />
521
+ </div>
522
+ <div class="px-3 py-3">
523
+ <p class="iconID text-[11px] text-center text-gray-500 dark:text-gray-400 font-medium leading-tight truncate">${item}</p>
443
524
  </div>
444
- <div class="button-group flex flex-row items-center justify-center gap-2 mt-2">
445
- <button class="button flex items-center justify-center min-w-[80px] px-1.5 py-0.5 text-[11px] text-gray-400 border border-gray-300 dark:border-gray-700 rounded bg-white dark:bg-gray-800 font-normal hover:bg-blue-100 dark:hover:bg-blue-900 transition relative text-center" onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)" onclick="copyText(this)">
446
- Copy Dot Code<span class="tooltiptext left-1/2 -translate-x-1/2 bottom-10 shadow-md">Copy Dot Code</span>
447
- </button>
448
- <button class="button flex items-center justify-center min-w-[80px] px-1.5 py-0.5 text-[11px] text-gray-400 border border-gray-300 dark:border-gray-700 rounded bg-white dark:bg-gray-800 font-normal hover:bg-blue-100 dark:hover:bg-blue-900 transition relative text-center" onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)" onclick="copyCode(this)">
449
- Copy Code<span class="tooltiptext left-1/2 -translate-x-1/2 bottom-10 shadow-md">Copy Code</span>
450
- </button>
525
+ <div class="action-bar absolute bottom-0 inset-x-0 bg-white/97 dark:bg-gray-900/97 backdrop-blur-sm border-t border-gray-100 dark:border-gray-800 p-2 flex gap-1.5 translate-y-full group-hover:translate-y-0 transition-transform duration-150 ease-out">
526
+ <button class="button flex-1 py-1.5 text-[10px] font-semibold text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 rounded-lg hover:bg-blue-100 dark:hover:bg-blue-900/60 transition" onclick="event.stopPropagation();copyText(this)">Dot Code</button>
527
+ <button class="button flex-1 py-1.5 text-[10px] font-semibold text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-800 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition" onclick="event.stopPropagation();copyCode(this)">HTML</button>
451
528
  </div>
452
- <pre class="codeBlock"><code class="codeBlock">${codeString}</code></pre>
529
+ <pre class="codeBlock hidden"><code class="codeBlock">${htmlCode}</code></pre>
453
530
  `;
454
531
  list.appendChild(div);
455
532
  } else {
456
- // Illustration card (light/dark, dot-illustration wrapper)
533
+ // ── Illustration card ─────────────────────────────────────────────
457
534
  let categoryFolder = globalList.includes(item) ? 'global' : 'dashboards';
458
- const svgPath = `illustrations/${themeClass}/${categoryFolder}/${item}.svg`;
535
+ const svgPath = `illustrations/${themeClass}/${categoryFolder}/${item}.svg`;
459
536
  const codeString = `<span class="dot-illustration"><img src="${svgPath}" class="${item} ${themeClass}"/></span>`;
460
537
  const div = document.createElement('div');
461
- div.setAttribute('class', 'copy-container relative group flex flex-col items-center justify-between bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl shadow-lg p-2 md:p-3 transition-transform duration-200 hover:scale-105 hover:shadow-2xl h-[302px] w-full');
462
- div.dataset.itemId = item;
538
+ div.className = 'copy-container group relative bg-white dark:bg-gray-900 rounded-2xl overflow-hidden cursor-pointer shadow-sm transition-all duration-200 hover:shadow-md hover:ring-2 hover:ring-blue-400/30 dark:hover:ring-blue-500/30';
539
+ div.dataset.itemId = item;
463
540
  div.dataset.itemType = 'illustration';
464
541
  div.innerHTML = `
465
- <button class="absolute top-2 right-2 z-10 p-1 rounded-full bg-white/80 dark:bg-gray-800/80 hover:bg-yellow-100 dark:hover:bg-yellow-300 transition" title="Toggle favourite" onclick="event.stopPropagation();window.toggleFavourite('${item}')">
466
- <svg class="h-5 w-5" fill="${isFav ? 'gold' : 'none'}" viewBox="0 0 24 24" stroke="gold"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l2.036 6.29a1 1 0 00.95.69h6.631c.969 0 1.371 1.24.588 1.81l-5.37 3.905a1 1 0 00-.364 1.118l2.036 6.29c.3.921-.755 1.688-1.54 1.118l-5.37-3.905a1 1 0 00-1.176 0l-5.37 3.905c-.784.57-1.838-.197-1.54-1.118l2.036-6.29a1 1 0 00-.364-1.118L2.342 11.717c-.783-.57-.38-1.81.588-1.81h6.631a1 1 0 00.95-.69l2.036-6.29z"/></svg>
542
+ <button class="fav-btn absolute top-2.5 right-2.5 z-20 opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded-lg bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm shadow-sm" onclick="event.stopPropagation();window.toggleFavourite('${item}')">
543
+ <svg class="w-3.5 h-3.5" fill="${isFav ? '#FBBF24' : 'none'}" viewBox="0 0 24 24" stroke="${isFav ? '#FBBF24' : '#9CA3AF'}" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l2.036 6.29a1 1 0 00.95.69h6.631c.969 0 1.371 1.24.588 1.81l-5.37 3.905a1 1 0 00-.364 1.118l2.036 6.29c.3.921-.755 1.688-1.54 1.118l-5.37-3.905a1 1 0 00-1.176 0l-5.37 3.905c-.784.57-1.838-.197-1.54-1.118l2.036-6.29a1 1 0 00-.364-1.118L2.342 11.717c-.783-.57-.38-1.81.588-1.81h6.631a1 1 0 00.95-.69l2.036-6.29z"/></svg>
467
544
  </button>
468
- <div class="flex-1 flex flex-col justify-center items-center">
469
- <span class="dot-illustration"><img src="${svgPath}" class="${item} ${themeClass}"/></span>
470
- <span class="iconID block text-center text-base font-medium text-gray-800 dark:text-gray-200 my-2">${item}</span>
545
+ <div class="flex items-center justify-center h-36 px-4 dot-illustration">
546
+ <img src="${svgPath}" class="${item} ${themeClass}" style="width:120px;height:120px;object-fit:contain;display:block;" />
547
+ </div>
548
+ <div class="px-3 py-3">
549
+ <p class="iconID text-[11px] text-center text-gray-500 dark:text-gray-400 font-medium leading-tight break-words">${item}</p>
471
550
  </div>
472
- <div class="button-group flex flex-row items-center justify-center gap-2 mt-2">
473
- <button id="CopyID" class="button flex items-center justify-center min-w-[80px] px-1.5 py-0.5 text-[11px] text-gray-400 border border-gray-300 dark:border-gray-700 rounded bg-white dark:bg-gray-800 font-normal hover:bg-blue-100 dark:hover:bg-blue-900 transition relative text-center" onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)" onclick="copyText(this)">
474
- Copy Dot Code<span class="tooltiptext left-1/2 -translate-x-1/2 bottom-10 shadow-md">Copy Dot Code</span>
475
- </button>
476
- <button class="button flex items-center justify-center min-w-[80px] px-1.5 py-0.5 text-[11px] text-gray-400 border border-gray-300 dark:border-gray-700 rounded bg-white dark:bg-gray-800 font-normal hover:bg-blue-100 dark:hover:bg-blue-900 transition relative text-center" onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)" onclick="copyCode(this)">
477
- Copy Code<span class="tooltiptext left-1/2 -translate-x-1/2 bottom-10 shadow-md">Copy Code</span>
478
- </button>
551
+ <div class="action-bar absolute bottom-0 inset-x-0 bg-white/97 dark:bg-gray-900/97 backdrop-blur-sm border-t border-gray-100 dark:border-gray-800 p-2 flex gap-1.5 translate-y-full group-hover:translate-y-0 transition-transform duration-150 ease-out">
552
+ <button class="button flex-1 py-1.5 text-[10px] font-semibold text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 rounded-lg hover:bg-blue-100 dark:hover:bg-blue-900/60 transition" onclick="event.stopPropagation();copyText(this)">Dot Code</button>
553
+ <button class="button flex-1 py-1.5 text-[10px] font-semibold text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-800 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition" onclick="event.stopPropagation();copyCode(this)">HTML</button>
479
554
  </div>
480
- <pre class="codeBlock"><code class="codeBlock">${codeString}</code></pre>
555
+ <pre class="codeBlock hidden"><code class="codeBlock">${codeString}</code></pre>
481
556
  `;
482
557
  list.appendChild(div);
483
558
  }
@@ -590,7 +665,10 @@ document.addEventListener('DOMContentLoaded', () => {
590
665
  }
591
666
  });
592
667
 
593
- // No session restore token is in-memory only for security
668
+ // Auto-connect via local proxy (node demo/local-proxy.js) no token input needed
669
+ window.GitHubUpload?.tryLocalProxy?.().then(user => {
670
+ if (user) { updateAuthBadge(user); updateUploadAuthUI(); }
671
+ });
594
672
 
595
673
  // ── Upload panel ─────────────────────────────────────────────────────────
596
674
  const uploadBufs = { illusLight: null, illusDark: null, integSvg: null };
@@ -678,8 +756,12 @@ document.addEventListener('DOMContentLoaded', () => {
678
756
  document.getElementById('upload-illus-btn')?.addEventListener('click', handleIllusUpload);
679
757
  document.getElementById('upload-integ-btn')?.addEventListener('click', handleIntegUpload);
680
758
 
681
- // Set initial tab state to 'all' and update UI (this will also render illustrations)
682
- setTab('all');
759
+ // Initial render start on illustrations, alphabet hidden
760
+ const strip = document.getElementById('alphabet-strip');
761
+ if (strip) strip.style.display = 'none';
762
+ updateSectionUI('illustrations');
763
+ updateTabUI('all');
764
+ renderIllustrations();
683
765
 
684
766
  // --- FLOATING BAR LOGIC ---
685
767
  const floatingBar = document.getElementById('floating-bar');
@@ -709,13 +791,12 @@ function fuzzyMatch(str, query) {
709
791
 
710
792
  function getSuggestions(query) {
711
793
  if (!query) return [];
712
- // Use current tab's list
713
794
  let pool = [];
714
- if (currentTab === 'all') pool = illustrations;
715
- else if (currentTab === 'global') pool = globalList;
716
- else if (currentTab === 'dashboards') pool = dashboardsList;
717
- else if (currentTab === 'integrations') pool = integrationsList;
718
- else if (currentTab === 'favourites') pool = getFavourites();
795
+ if (currentSection === 'integrations') pool = integrationsList;
796
+ else if (currentTab === 'all') pool = illustrations;
797
+ else if (currentTab === 'global') pool = globalList;
798
+ else if (currentTab === 'dashboards') pool = dashboardsList;
799
+ else if (currentTab === 'favourites') pool = illustrations.filter(id => getFavourites().includes(id));
719
800
  // Fuzzy match, sort by closeness (shorter index of first match, then length)
720
801
  let scored = pool
721
802
  .map(id => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digital-ai/dot-illustrations",
3
- "version": "2.0.36",
3
+ "version": "2.0.38",
4
4
  "description": "A central place for the design team to keep illustrations and for dev teams to find them.",
5
5
  "main": "./index.css",
6
6
  "scripts": {