@digital-ai/dot-illustrations 2.0.35 → 2.0.37

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.
@@ -15,23 +15,54 @@ const GITHUB_UPLOAD_CONFIG = {
15
15
  REPO_NAME: 'dot-illustrations',
16
16
  BASE_BRANCH: 'main',
17
17
  SCOPES: 'public_repo',
18
+ LOCAL_PROXY: 'http://localhost:3001', // run: node demo/local-proxy.js
18
19
  };
19
20
 
20
- const STORAGE_TOKEN_KEY = 'gh_illus_token';
21
- const STORAGE_USER_KEY = 'gh_illus_user';
21
+ // ─── In-memory only — never stored, cleared on page close ────────────────────
22
22
 
23
- // ─── Token helpers ──────────────────────────────────────────────────────────
23
+ let _ghToken = null;
24
+ let _ghUser = null;
25
+ let _usingProxy = false;
24
26
 
25
- function ghGetToken() { return localStorage.getItem(STORAGE_TOKEN_KEY); }
26
- function ghSetToken(t) { localStorage.setItem(STORAGE_TOKEN_KEY, t); }
27
- function ghClearToken() {
28
- localStorage.removeItem(STORAGE_TOKEN_KEY);
29
- localStorage.removeItem(STORAGE_USER_KEY);
27
+ function ghGetToken() { return _ghToken; }
28
+ function ghSetToken(t) { _ghToken = t; }
29
+ function ghClearToken() { _ghToken = null; _ghUser = null; _usingProxy = false; }
30
+
31
+ // ─── Auto-connect via local proxy ────────────────────────────────────────────
32
+ // Called once on page load. If `node demo/local-proxy.js` is running, the user
33
+ // is authenticated automatically using their existing `gh auth` credentials —
34
+ // no token input required.
35
+
36
+ async function ghTryLocalProxy() {
37
+ try {
38
+ const resp = await fetch(`${GITHUB_UPLOAD_CONFIG.LOCAL_PROXY}/whoami`, {
39
+ signal: AbortSignal.timeout(900),
40
+ });
41
+ if (!resp.ok) return null;
42
+ const data = await resp.json();
43
+ if (data.login) {
44
+ _usingProxy = true;
45
+ _ghToken = '__proxy__'; // sentinel; actual auth lives in the proxy process
46
+ return data;
47
+ }
48
+ } catch { /* proxy not running */ }
49
+ return null;
30
50
  }
31
51
 
32
52
  // ─── GitHub API helpers ──────────────────────────────────────────────────────
33
53
 
34
54
  async function ghApi(path, opts = {}) {
55
+ // Route through local proxy when available (no token exposed to browser)
56
+ if (_usingProxy) {
57
+ const resp = await fetch(`${GITHUB_UPLOAD_CONFIG.LOCAL_PROXY}${path}`, {
58
+ ...opts,
59
+ headers: { 'Content-Type': 'application/json', ...(opts.headers || {}) },
60
+ });
61
+ const data = await resp.json();
62
+ if (!resp.ok) throw new Error(data.message || `GitHub API error ${resp.status}`);
63
+ return data;
64
+ }
65
+
35
66
  const token = ghGetToken();
36
67
  const resp = await fetch(`https://api.github.com${path}`, {
37
68
  ...opts,
@@ -282,6 +313,7 @@ window.GitHubUpload = {
282
313
  setToken: ghSetToken,
283
314
  clearToken: ghClearToken,
284
315
  getUser: ghGetUser,
316
+ tryLocalProxy: ghTryLocalProxy,
285
317
  startDeviceFlow: ghStartDeviceFlow,
286
318
  pollForToken: ghPollForToken,
287
319
  createIllustrationPR: ghCreateIllustrationPR,
package/demo/index.html CHANGED
@@ -3,226 +3,280 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <title>Dot Illustrations Library</title>
6
- <!-- Tailwind CSS CDN -->
7
6
  <script src="https://cdn.tailwindcss.com"></script>
8
- <!-- Heroicons CDN for Tailwind icons -->
9
- <script src="https://unpkg.com/@heroicons/vue@2.0.18/dist/heroicons.min.js"></script>
10
7
  <link rel="stylesheet" href="../index.css" />
11
8
  <link rel="stylesheet" href="./demo.css" />
12
9
  <script src="./github-upload.js" defer></script>
13
10
  <script src="script.js" defer></script>
14
- <script>
15
- // Tailwind dark mode config
16
- tailwind.config = { darkMode: 'class' };
17
- </script>
11
+ <script>tailwind.config = { darkMode: 'class' };</script>
18
12
  </head>
19
- <body class="bg-gray-50 dark:bg-gray-900 min-h-screen transition-colors duration-300">
20
- <div id="floating-bar" class="fixed top-0 left-0 w-full z-50 flex items-center justify-center bg-white/90 dark:bg-gray-900/90 shadow-md px-2 py-1 transition-transform duration-300 -translate-y-full" style="backdrop-filter: blur(6px);">
21
- <div class="flex flex-row items-center gap-2 w-full max-w-5xl">
22
- <input class="floating-search flex-1 px-2 py-1 border border-gray-300 dark:border-gray-700 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm" type="text" id="floatingInput" autocomplete="off" placeholder="Search...">
23
- <nav class="inline-flex rounded shadow overflow-hidden border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 overflow-x-auto" id="floating-tabs" role="tablist">
24
- <button class="floating-tab tab-btn whitespace-nowrap px-3 py-1 text-xs font-medium focus:outline-none transition-colors duration-200 text-blue-600 dark:text-blue-400 bg-blue-100 dark:bg-blue-900" id="floating-tab-all" data-tab="all" aria-selected="true" role="tab">All</button>
25
- <button class="floating-tab tab-btn whitespace-nowrap px-3 py-1 text-xs font-medium focus:outline-none transition-colors duration-200 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700" id="floating-tab-global" data-tab="global" aria-selected="false" role="tab">Global</button>
26
- <button class="floating-tab tab-btn whitespace-nowrap px-3 py-1 text-xs font-medium focus:outline-none transition-colors duration-200 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700" id="floating-tab-dashboards" data-tab="dashboards" aria-selected="false" role="tab">Dashboards</button>
27
- <button class="floating-tab tab-btn whitespace-nowrap px-3 py-1 text-xs font-medium focus:outline-none transition-colors duration-200 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700" id="floating-tab-integrations" data-tab="integrations" aria-selected="false" role="tab">Integrations</button>
28
- <button class="floating-tab tab-btn whitespace-nowrap px-3 py-1 text-xs font-medium focus:outline-none transition-colors duration-200 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700" id="floating-tab-favourites" data-tab="favourites" aria-selected="false" role="tab">Favourites</button>
29
- <button class="floating-tab tab-btn whitespace-nowrap px-3 py-1 text-xs font-medium focus:outline-none transition-colors duration-200 text-green-700 dark:text-green-400 hover:bg-green-50 dark:hover:bg-green-900/40" id="floating-tab-upload" data-tab="upload" aria-selected="false" role="tab">+ Upload</button>
13
+
14
+ <body class="bg-gray-50 dark:bg-gray-950 min-h-screen transition-colors duration-300 antialiased">
15
+
16
+ <!-- ── Sticky top bar (appears on scroll) ────────────────────────────── -->
17
+ <div id="floating-bar" class="fixed top-0 left-0 w-full z-50 bg-white/95 dark:bg-gray-900/95 border-b border-gray-200 dark:border-gray-800 px-4 py-2 shadow-sm" style="backdrop-filter:blur(8px);">
18
+ <div class="flex items-center gap-3 max-w-7xl mx-auto">
19
+ <input id="floatingInput" type="text" autocomplete="off" placeholder="Search…"
20
+ class="flex-1 min-w-0 px-3 py-1.5 text-sm bg-gray-100 dark:bg-gray-800 border border-transparent focus:border-blue-500 focus:bg-white dark:focus:bg-gray-900 rounded-lg outline-none text-gray-900 dark:text-white transition">
21
+ <div class="flex gap-1 shrink-0" id="floating-section-btns">
22
+ <button data-section="illustrations" class="floating-section-btn px-3 py-1.5 text-xs font-medium rounded-lg bg-blue-600 text-white focus:outline-none transition">Illustrations</button>
23
+ <button data-section="integrations" class="floating-section-btn px-3 py-1.5 text-xs font-medium rounded-lg bg-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none transition">Integrations</button>
24
+ </div>
25
+ <nav id="floating-tabs" class="hidden items-center gap-0.5 bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5" role="tablist">
26
+ <!-- injected by JS -->
30
27
  </nav>
31
28
  </div>
32
29
  </div>
33
- <div class="container mx-auto px-4 py-8">
34
- <div class="flex flex-col md:flex-row items-center justify-between mb-6 gap-4">
35
- <h1 class="text-3xl md:text-5xl font-bold text-center md:text-left text-gray-900 dark:text-white">Dot Illustrations</h1>
36
- <div class="flex items-center gap-3">
37
- <!-- GitHub connect -->
38
- <div id="gh-auth-area">
39
- <button id="gh-connect-btn" class="flex items-center gap-2 px-3 py-2 bg-gray-900 dark:bg-gray-700 text-white rounded-full shadow hover:bg-gray-700 dark:hover:bg-gray-600 transition text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
40
- <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0 1 12 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.015 2.898-.015 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
30
+
31
+ <!-- ── Main header ────────────────────────────────────────────────────── -->
32
+ <header class="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 px-4 py-4">
33
+ <div class="max-w-7xl mx-auto flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
34
+
35
+ <!-- Title + subtitle -->
36
+ <div>
37
+ <h1 class="text-2xl font-bold text-gray-900 dark:text-white tracking-tight">Dot Illustrations</h1>
38
+ <p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
39
+ <a href="https://github.com/digital-ai/dot-illustrations" target="_blank" class="hover:text-blue-600 dark:hover:text-blue-400 transition">github.com/digital-ai/dot-illustrations</a>
40
+ &nbsp;·&nbsp;
41
+ <code class="font-mono">link rel="stylesheet" href="./index.css"</code>
42
+ </p>
43
+ </div>
44
+
45
+ <!-- Toolbar: Upload · GitHub · Theme -->
46
+ <div class="flex items-center gap-2 shrink-0">
47
+
48
+ <!-- Upload New -->
49
+ <button id="upload-new-btn"
50
+ class="flex items-center gap-1.5 px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg shadow-sm transition focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1">
51
+ <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"/></svg>
52
+ Upload New
53
+ </button>
54
+
55
+ <!-- GitHub auth -->
56
+ <div id="gh-auth-area" class="flex">
57
+ <button id="gh-connect-btn"
58
+ class="flex items-center gap-1.5 px-3 py-2 bg-gray-900 dark:bg-gray-700 hover:bg-gray-800 dark:hover:bg-gray-600 text-white text-sm font-medium rounded-lg shadow-sm transition focus:outline-none focus:ring-2 focus:ring-gray-500">
59
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.015 2.898-.015 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
41
60
  Connect GitHub
42
61
  </button>
43
- <span id="gh-user-badge" class="hidden items-center gap-2 px-3 py-2 bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded-full text-sm">
44
- <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
62
+ <span id="gh-user-badge" class="hidden items-center gap-2 px-3 py-2 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-800 text-green-800 dark:text-green-300 text-sm font-medium rounded-lg">
63
+ <svg class="w-4 h-4 text-green-500" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
45
64
  <span id="gh-user-name"></span>
46
- <button id="gh-logout-btn" class="ml-1 text-xs underline opacity-70 hover:opacity-100">Log out</button>
65
+ <button id="gh-logout-btn" class="ml-1 text-xs opacity-50 hover:opacity-100 transition">Log out</button>
47
66
  </span>
48
67
  </div>
49
- <!-- Dark mode toggle -->
50
- <button id="theme-toggle" aria-label="Toggle dark mode" class="flex items-center gap-2 px-4 py-2 bg-gray-200 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-full shadow hover:bg-gray-300 dark:hover:bg-gray-700 transition focus:outline-none focus:ring-2 focus:ring-blue-500">
51
- <span id="theme-icon" class="transition-transform duration-300"></span>
52
- <span class="hidden sm:inline">Switch theme</span>
68
+
69
+ <!-- Dark mode -->
70
+ <button id="theme-toggle" aria-label="Toggle dark mode"
71
+ class="p-2 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-lg transition focus:outline-none focus:ring-2 focus:ring-gray-400">
72
+ <span id="theme-icon"></span>
53
73
  </button>
54
74
  </div>
55
75
  </div>
56
- <p class="text-center text-gray-700 dark:text-gray-300 mb-2">
57
- For more details please see
58
- <a href="https://github.com/digital-ai/dot-illustrations" target="_blank" class="text-blue-600 dark:text-blue-400 underline hover:text-blue-800 dark:hover:text-blue-200 transition">https://github.com/digital-ai/dot-illustrations</a>
59
- </p>
60
- <p class="text-center text-gray-600 dark:text-gray-400 mb-6">For using illustrations as HTML include <code class="bg-gray-200 dark:bg-gray-800 px-2 py-1 rounded text-sm font-mono">link rel="stylesheet" href="./index.css"</code></p>
61
- <div class="flex flex-col md:flex-row items-center justify-center gap-4 mb-8 relative">
62
- <input class="search-wrapper w-full md:w-1/2 px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-white transition" type="text" id="myInput" autocomplete="off" placeholder="Search for illustrations...">
63
- <div id="search-suggestions" class="absolute top-full left-1/2 -translate-x-1/2 w-full md:w-1/2 z-50 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-b-lg shadow-lg hidden max-h-72 overflow-y-auto"></div>
76
+ </header>
77
+
78
+ <!-- ── Page body ──────────────────────────────────────────────────────── -->
79
+ <main class="max-w-7xl mx-auto px-4 py-6">
80
+
81
+ <!-- Search -->
82
+ <div class="relative mb-5 max-w-xl">
83
+ <svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"/></svg>
84
+ <input id="myInput" type="text" autocomplete="off" placeholder="Search illustrations and integrations…"
85
+ class="w-full pl-10 pr-4 py-2.5 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl text-sm text-gray-900 dark:text-white outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 transition">
86
+ <div id="search-suggestions" class="absolute top-full left-0 right-0 z-50 mt-1 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-lg hidden max-h-64 overflow-y-auto"></div>
64
87
  </div>
65
- <!-- Tabs -->
66
- <div class="mb-4 flex justify-center overflow-x-auto">
67
- <nav class="inline-flex rounded-lg shadow overflow-hidden border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shrink-0" role="tablist" id="main-tabs">
68
- <button class="tab-btn whitespace-nowrap px-5 py-2 text-sm font-medium focus:outline-none transition-colors duration-200 text-blue-600 dark:text-blue-400 bg-blue-100 dark:bg-blue-900" id="tab-all" data-tab="all" aria-selected="true" role="tab">All Illustrations</button>
69
- <button class="tab-btn whitespace-nowrap px-5 py-2 text-sm font-medium focus:outline-none transition-colors duration-200 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700" id="tab-global" data-tab="global" aria-selected="false" role="tab">Global</button>
70
- <button class="tab-btn whitespace-nowrap px-5 py-2 text-sm font-medium focus:outline-none transition-colors duration-200 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700" id="tab-dashboards" data-tab="dashboards" aria-selected="false" role="tab">Dashboards</button>
71
- <button class="tab-btn whitespace-nowrap px-5 py-2 text-sm font-medium focus:outline-none transition-colors duration-200 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700" id="tab-integrations" data-tab="integrations" aria-selected="false" role="tab">Integrations</button>
72
- <button class="tab-btn whitespace-nowrap px-5 py-2 text-sm font-medium focus:outline-none transition-colors duration-200 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700" id="tab-favourites" data-tab="favourites" aria-selected="false" role="tab">Favourites</button>
73
- <button class="tab-btn whitespace-nowrap px-5 py-2 text-sm font-medium focus:outline-none transition-colors duration-200 text-green-700 dark:text-green-400 hover:bg-green-50 dark:hover:bg-green-900/40" id="tab-upload" data-tab="upload" aria-selected="false" role="tab">+ Upload New</button>
74
- </nav>
88
+
89
+ <!-- Section switcher: Illustrations | Integrations -->
90
+ <div class="flex items-center gap-1 mb-5 bg-gray-100 dark:bg-gray-800 rounded-xl p-1 w-fit" id="section-switcher">
91
+ <button id="section-illustrations" data-section="illustrations"
92
+ class="section-btn px-5 py-2 text-sm font-semibold rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm focus:outline-none transition">
93
+ 🖼 Illustrations
94
+ </button>
95
+ <button id="section-integrations" data-section="integrations"
96
+ class="section-btn px-5 py-2 text-sm font-semibold rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white focus:outline-none transition">
97
+ 🔌 Integrations
98
+ </button>
75
99
  </div>
76
100
 
77
- <!-- Alphabet filter strip -->
78
- <div id="alphabet-strip" class="hidden mb-4 flex flex-wrap justify-center gap-1 px-2">
79
- <button class="alpha-btn px-2 py-0.5 text-xs rounded font-mono bg-blue-600 text-white" data-letter="all">All</button>
101
+ <!-- Illustration sub-tabs -->
102
+ <div id="illus-sub-tabs" class="flex items-center gap-1 mb-4 bg-gray-100 dark:bg-gray-800 rounded-xl p-1 w-fit" role="tablist">
103
+ <button class="tab-btn px-4 py-1.5 text-sm font-medium rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm focus:outline-none transition" data-tab="all" role="tab">All</button>
104
+ <button class="tab-btn px-4 py-1.5 text-sm font-medium rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white focus:outline-none transition" data-tab="global" role="tab">Global</button>
105
+ <button class="tab-btn px-4 py-1.5 text-sm font-medium rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white focus:outline-none transition" data-tab="dashboards" role="tab">Dashboards</button>
106
+ <button class="tab-btn px-4 py-1.5 text-sm font-medium rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white focus:outline-none transition" data-tab="favourites" role="tab">Favourites ⭐</button>
80
107
  </div>
81
108
 
82
- <!-- Result count + integration note -->
83
- <p id="result-count" class="text-center text-sm text-gray-500 dark:text-gray-400 mb-4"></p>
84
- <p id="integrations-note" class="hidden text-center text-xs text-gray-500 dark:text-gray-400 mb-4">
85
- Integration logos are single-theme (no dark variant). Use <code class="font-mono bg-gray-200 dark:bg-gray-800 px-1 rounded">.dot-integration</code> wrapper.
86
- </p>
109
+ <!-- Alphabet filter -->
110
+ <div id="alphabet-strip" class="hidden flex flex-wrap gap-1 mb-4"></div>
111
+
112
+ <!-- Result count -->
113
+ <p id="result-count" class="text-xs text-gray-400 dark:text-gray-500 mb-3"></p>
87
114
 
88
115
  <!-- Grid -->
89
- <div id="illustration-list" class="demo-wrapper dot-illustration grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 gap-6 max-h-[calc(100vh-320px)] overflow-y-auto px-2">
90
- <!-- injected by JS -->
116
+ <div id="illustration-list"
117
+ class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-3 max-h-[calc(100vh-280px)] overflow-y-auto pr-1">
91
118
  </div>
92
119
 
93
- <!-- Upload panel -->
94
- <div id="upload-panel" class="hidden mt-8">
95
- <div class="max-w-2xl mx-auto grid md:grid-cols-2 gap-6">
120
+ <!-- Upload panel (slide-in below header) -->
121
+ <div id="upload-panel" class="hidden mt-6">
122
+ <div class="bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-700 shadow-lg p-6 max-w-2xl">
123
+ <h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-5">Upload new asset → Create PR</h2>
124
+ <div class="grid md:grid-cols-2 gap-6">
96
125
 
97
- <!-- Illustration upload -->
98
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 border border-gray-200 dark:border-gray-700 flex flex-col gap-4">
99
- <h2 class="text-lg font-semibold text-gray-900 dark:text-white">Upload Illustration PR</h2>
100
- <div id="upload-illus-auth-gate" class="p-3 bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-700 rounded-lg text-sm text-yellow-800 dark:text-yellow-200">
101
- Connect GitHub (header) to enable.
102
- </div>
103
- <div id="upload-illus-form" class="hidden flex flex-col gap-3">
104
- <div>
105
- <label class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1 block">Illustration ID <span class="text-red-500">*</span></label>
106
- <input id="upload-illus-id" type="text" placeholder="e.g. my-illustration" class="w-full px-3 py-1.5 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
126
+ <!-- Illustration upload -->
127
+ <div class="flex flex-col gap-3">
128
+ <h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 flex items-center gap-2">
129
+ <span class="w-5 h-5 bg-purple-100 dark:bg-purple-900/40 text-purple-700 dark:text-purple-300 rounded text-xs flex items-center justify-center">🖼</span>
130
+ Illustration (light + dark)
131
+ </h3>
132
+ <div id="upload-illus-auth-gate" class="flex items-center gap-2 p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg text-xs text-amber-700 dark:text-amber-300">
133
+ <svg class="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/></svg>
134
+ Connect GitHub to enable uploads
107
135
  </div>
108
- <div>
109
- <label class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1 block">Category</label>
110
- <select id="upload-illus-category" class="w-full px-3 py-1.5 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
111
- <option value="global" selected>Global</option>
136
+ <div id="upload-illus-form" class="hidden flex flex-col gap-3">
137
+ <input id="upload-illus-id" type="text" placeholder="Illustration ID (e.g. my-illustration)"
138
+ class="w-full px-3 py-2 text-sm bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white outline-none focus:border-blue-500 transition">
139
+ <select id="upload-illus-category"
140
+ class="w-full px-3 py-2 text-sm bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white outline-none focus:border-blue-500 transition">
141
+ <option value="global">Global</option>
112
142
  <option value="dashboards">Dashboards</option>
113
143
  </select>
114
- </div>
115
- <div class="grid grid-cols-2 gap-3">
116
- <div>
117
- <label class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1 block">☀️ Light SVG <span class="text-red-500">*</span></label>
118
- <div id="illus-light-drop" class="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-3 text-center cursor-pointer hover:border-blue-400 transition text-xs text-gray-500 dark:text-gray-400">
119
- Drop or click
120
- <input id="illus-light-input" type="file" accept=".svg" class="hidden" />
121
- </div>
122
- <p id="illus-light-name" class="text-xs text-green-600 dark:text-green-400 mt-1 truncate hidden"></p>
144
+ <div class="grid grid-cols-2 gap-2">
145
+ <label class="flex flex-col gap-1">
146
+ <span class="text-xs text-gray-500 dark:text-gray-400">☀️ Light SVG</span>
147
+ <div id="illus-light-drop" class="border-2 border-dashed border-gray-200 dark:border-gray-700 rounded-lg p-3 text-center text-xs text-gray-400 cursor-pointer hover:border-blue-400 transition">
148
+ Drop or click<input id="illus-light-input" type="file" accept=".svg" class="hidden">
149
+ </div>
150
+ <p id="illus-light-name" class="hidden text-xs text-green-600 dark:text-green-400 truncate"></p>
151
+ </label>
152
+ <label class="flex flex-col gap-1">
153
+ <span class="text-xs text-gray-500 dark:text-gray-400">🌙 Dark SVG</span>
154
+ <div id="illus-dark-drop" class="border-2 border-dashed border-gray-200 dark:border-gray-700 rounded-lg p-3 text-center text-xs text-gray-400 cursor-pointer hover:border-blue-400 transition">
155
+ Drop or click<input id="illus-dark-input" type="file" accept=".svg" class="hidden">
156
+ </div>
157
+ <p id="illus-dark-name" class="hidden text-xs text-green-600 dark:text-green-400 truncate"></p>
158
+ </label>
123
159
  </div>
124
- <div>
125
- <label class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1 block">🌙 Dark SVG <span class="text-red-500">*</span></label>
126
- <div id="illus-dark-drop" class="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-3 text-center cursor-pointer hover:border-blue-400 transition text-xs text-gray-500 dark:text-gray-400">
127
- Drop or click
128
- <input id="illus-dark-input" type="file" accept=".svg" class="hidden" />
129
- </div>
130
- <p id="illus-dark-name" class="text-xs text-green-600 dark:text-green-400 mt-1 truncate hidden"></p>
160
+ <button id="upload-illus-btn"
161
+ class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition disabled:opacity-50">
162
+ Create Pull Request
163
+ </button>
164
+ <div id="upload-illus-status" class="hidden text-xs text-center"></div>
165
+ <div id="upload-illus-pr-link" class="hidden">
166
+ <a id="upload-illus-pr-anchor" href="#" target="_blank"
167
+ class="flex items-center justify-center gap-2 px-4 py-2 bg-green-600 hover:bg-green-700 text-white text-sm font-medium rounded-lg transition">
168
+ View Pull Request ↗
169
+ </a>
131
170
  </div>
132
171
  </div>
133
- <button id="upload-illus-btn" class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium text-sm transition focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50">Create PR</button>
134
- <div id="upload-illus-status" class="hidden text-sm text-center"></div>
135
- <div id="upload-illus-pr-link" class="hidden text-center">
136
- <a id="upload-illus-pr-anchor" href="#" target="_blank" class="inline-flex items-center gap-2 px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white rounded-lg font-medium text-sm transition">View Pull Request ↗</a>
137
- </div>
138
172
  </div>
139
- </div>
140
173
 
141
- <!-- Integration upload -->
142
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 border border-gray-200 dark:border-gray-700 flex flex-col gap-4">
143
- <h2 class="text-lg font-semibold text-gray-900 dark:text-white">Upload Integration PR</h2>
144
- <div id="upload-integ-auth-gate" class="p-3 bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-700 rounded-lg text-sm text-yellow-800 dark:text-yellow-200">
145
- Connect GitHub (header) to enable.
146
- </div>
147
- <div id="upload-integ-form" class="hidden flex flex-col gap-3">
148
- <div>
149
- <label class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1 block">Integration ID <span class="text-red-500">*</span></label>
150
- <input id="upload-integ-id" type="text" placeholder="e.g. my-tool" class="w-full px-3 py-1.5 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
174
+ <!-- Integration upload -->
175
+ <div class="flex flex-col gap-3">
176
+ <h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 flex items-center gap-2">
177
+ <span class="w-5 h-5 bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300 rounded text-xs flex items-center justify-center">🔌</span>
178
+ Integration logo (single SVG)
179
+ </h3>
180
+ <div id="upload-integ-auth-gate" class="flex items-center gap-2 p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg text-xs text-amber-700 dark:text-amber-300">
181
+ <svg class="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/></svg>
182
+ Connect GitHub to enable uploads
151
183
  </div>
152
- <div>
153
- <label class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1 block">SVG file (single, no dark variant) <span class="text-red-500">*</span></label>
154
- <div id="integ-svg-drop" class="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-4 text-center cursor-pointer hover:border-blue-400 transition text-sm text-gray-500 dark:text-gray-400">
155
- Drop SVG here or click to browse
156
- <input id="integ-svg-input" type="file" accept=".svg" class="hidden" />
184
+ <div id="upload-integ-form" class="hidden flex flex-col gap-3">
185
+ <input id="upload-integ-id" type="text" placeholder="Integration ID (e.g. my-tool)"
186
+ class="w-full px-3 py-2 text-sm bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white outline-none focus:border-blue-500 transition">
187
+ <div id="integ-svg-drop" class="border-2 border-dashed border-gray-200 dark:border-gray-700 rounded-lg p-4 text-center text-sm text-gray-400 cursor-pointer hover:border-blue-400 transition">
188
+ Drop SVG here or click to browse<input id="integ-svg-input" type="file" accept=".svg" class="hidden">
189
+ </div>
190
+ <p id="integ-svg-name" class="hidden text-xs text-green-600 dark:text-green-400 truncate"></p>
191
+ <p class="text-xs text-gray-400 dark:text-gray-500">Single-theme only — no dark variant for integrations.</p>
192
+ <button id="upload-integ-btn"
193
+ class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition disabled:opacity-50">
194
+ Create Pull Request
195
+ </button>
196
+ <div id="upload-integ-status" class="hidden text-xs text-center"></div>
197
+ <div id="upload-integ-pr-link" class="hidden">
198
+ <a id="upload-integ-pr-anchor" href="#" target="_blank"
199
+ class="flex items-center justify-center gap-2 px-4 py-2 bg-green-600 hover:bg-green-700 text-white text-sm font-medium rounded-lg transition">
200
+ View Pull Request ↗
201
+ </a>
157
202
  </div>
158
- <p id="integ-svg-name" class="text-xs text-green-600 dark:text-green-400 mt-1 truncate hidden"></p>
159
- </div>
160
- <div class="p-3 bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-700 rounded-lg text-xs text-blue-800 dark:text-blue-200">
161
- Integration logos have no dark variant. Use a square, brand-accurate SVG.
162
- </div>
163
- <button id="upload-integ-btn" class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium text-sm transition focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50">Create PR</button>
164
- <div id="upload-integ-status" class="hidden text-sm text-center"></div>
165
- <div id="upload-integ-pr-link" class="hidden text-center">
166
- <a id="upload-integ-pr-anchor" href="#" target="_blank" class="inline-flex items-center gap-2 px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white rounded-lg font-medium text-sm transition">View Pull Request ↗</a>
167
203
  </div>
168
204
  </div>
205
+
169
206
  </div>
170
207
  </div>
171
208
  </div>
172
- </div>
173
- <!-- Modal for illustration details -->
174
- <div id="illustration-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 dark:bg-black/70 hidden">
175
- <div class="bg-white dark:bg-gray-900 rounded-xl shadow-2xl max-w-lg w-full p-6 relative flex flex-col items-center">
176
- <button id="modal-close" class="absolute top-3 right-3 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 text-2xl font-bold focus:outline-none">&times;</button>
177
- <h2 class="text-xl font-semibold mb-4 w-full text-center text-gray-900 dark:text-gray-100">Illustration preview</h2>
178
- <div id="modal-image" class="mb-4"></div>
179
- <div class="w-full flex flex-col gap-2 mb-2">
180
- <div class="text-base font-semibold text-center text-gray-800 dark:text-gray-100 mb-1" id="modal-illustration-name"></div>
181
- </div>
182
- <div class="w-full flex flex-col gap-2 mb-2">
183
- <label class="text-xs font-medium text-gray-500 dark:text-gray-400" for="modal-dot-code">Dot Code</label>
184
- <div class="flex flex-row items-center gap-2">
185
- <textarea id="modal-dot-code" class="flex-1 px-2 py-1 border border-gray-300 dark:border-gray-700 rounded bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white text-xs font-mono resize-none" rows="2" readonly></textarea>
186
- <button id="modal-copy-dot-code" class="button flex items-center justify-center px-2 py-1 text-xs 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" title="Copy Dot Code">Copy Dot Code</button>
209
+
210
+ </main>
211
+
212
+ <!-- ── Illustration detail modal ──────────────────────────────────────── -->
213
+ <div id="illustration-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 dark:bg-black/60 backdrop-blur-sm hidden">
214
+ <div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl max-w-md w-full mx-4 p-6 relative flex flex-col items-center border border-gray-200 dark:border-gray-700">
215
+ <button id="modal-close" class="absolute top-4 right-4 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition">
216
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
217
+ </button>
218
+ <div id="modal-image" class="mb-4 p-4 bg-gray-50 dark:bg-gray-800 rounded-xl"></div>
219
+ <p class="text-base font-semibold text-gray-900 dark:text-white mb-4" id="modal-illustration-name"></p>
220
+ <div class="w-full space-y-3 mb-4">
221
+ <div>
222
+ <label class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1 block">Dot Code</label>
223
+ <div class="flex gap-2">
224
+ <textarea id="modal-dot-code" readonly rows="2"
225
+ class="flex-1 px-3 py-2 text-xs font-mono bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white resize-none outline-none"></textarea>
226
+ <button id="modal-copy-dot-code"
227
+ class="px-3 py-1 text-xs bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-blue-50 dark:hover:bg-blue-900/30 transition">Copy</button>
228
+ </div>
187
229
  </div>
188
- </div>
189
- <div class="w-full flex flex-col gap-2 mb-2">
190
- <label class="text-xs font-medium text-gray-500 dark:text-gray-400" for="modal-code">HTML Code</label>
191
- <div class="flex flex-row items-center gap-2">
192
- <textarea id="modal-code" class="flex-1 px-2 py-1 border border-gray-300 dark:border-gray-700 rounded bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white text-xs font-mono resize-none" rows="2" readonly></textarea>
193
- <button id="modal-copy-code" class="button flex items-center justify-center px-2 py-1 text-xs 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">Copy Code</button>
230
+ <div>
231
+ <label class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1 block">HTML Code</label>
232
+ <div class="flex gap-2">
233
+ <textarea id="modal-code" readonly rows="2"
234
+ class="flex-1 px-3 py-2 text-xs font-mono bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white resize-none outline-none"></textarea>
235
+ <button id="modal-copy-code"
236
+ class="px-3 py-1 text-xs bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-blue-50 dark:hover:bg-blue-900/30 transition">Copy</button>
237
+ </div>
194
238
  </div>
195
239
  </div>
196
- <button id="modal-close-bottom" class="mt-4 px-4 py-2 rounded bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-medium hover:bg-gray-300 dark:hover:bg-gray-600 transition">Close</button>
240
+ <button id="modal-close-bottom"
241
+ class="px-5 py-2 text-sm bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition">Close</button>
197
242
  </div>
198
243
  </div>
199
- <!-- GitHub Device Flow modal -->
200
- <div id="gh-device-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 dark:bg-black/70 hidden">
201
- <div class="bg-white dark:bg-gray-900 rounded-xl shadow-2xl max-w-sm w-full p-6 relative flex flex-col items-center mx-4">
202
- <button id="gh-device-close" class="absolute top-3 right-3 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 text-2xl font-bold focus:outline-none">&times;</button>
203
- <svg class="w-10 h-10 mb-4 text-gray-800 dark:text-gray-200" fill="currentColor" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0 1 12 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.015 2.898-.015 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
204
- <h2 class="text-xl font-bold text-gray-900 dark:text-white mb-2">Connect GitHub</h2>
205
- <div id="gh-device-step-code" class="w-full flex flex-col items-center gap-3">
206
- <p class="text-sm text-gray-600 dark:text-gray-400 text-center">Open the URL below and enter the code to authorise.</p>
207
- <a id="gh-device-uri" href="#" target="_blank" class="text-blue-600 dark:text-blue-400 underline text-sm font-medium"></a>
208
- <div class="flex items-center gap-2">
209
- <code id="gh-device-code" class="text-2xl font-mono tracking-widest bg-gray-100 dark:bg-gray-800 px-4 py-2 rounded-lg text-gray-900 dark:text-white"></code>
210
- <button id="gh-copy-code-btn" class="text-xs px-2 py-1 bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition">Copy</button>
244
+
245
+ <!-- ── GitHub Device Flow modal ───────────────────────────────────────── -->
246
+ <div id="gh-device-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 dark:bg-black/60 backdrop-blur-sm hidden">
247
+ <div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl max-w-sm w-full mx-4 p-6 relative border border-gray-200 dark:border-gray-700">
248
+ <button id="gh-device-close" class="absolute top-4 right-4 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition">
249
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
250
+ </button>
251
+ <div class="flex flex-col items-center gap-4">
252
+ <svg class="w-10 h-10 text-gray-800 dark:text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.015 2.898-.015 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
253
+ <h2 class="text-xl font-bold text-gray-900 dark:text-white">Connect GitHub</h2>
254
+
255
+ <div id="gh-device-step-code" class="w-full flex flex-col items-center gap-3">
256
+ <p class="text-sm text-gray-500 dark:text-gray-400 text-center">Open the URL below and enter the code.</p>
257
+ <a id="gh-device-uri" href="#" target="_blank" class="text-sm text-blue-600 dark:text-blue-400 underline font-medium"></a>
258
+ <div class="flex items-center gap-2">
259
+ <code id="gh-device-code" class="text-2xl font-mono tracking-widest bg-gray-100 dark:bg-gray-800 px-4 py-2 rounded-xl text-gray-900 dark:text-white"></code>
260
+ <button id="gh-copy-code-btn" class="text-xs px-2 py-1 bg-gray-200 dark:bg-gray-700 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition">Copy</button>
261
+ </div>
262
+ <div class="flex items-center gap-2 text-gray-400 text-sm">
263
+ <svg class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path></svg>
264
+ Waiting for authorisation…
265
+ </div>
211
266
  </div>
212
- <div class="flex items-center gap-2 text-gray-500 dark:text-gray-400 text-sm">
213
- <svg class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path></svg>
214
- Waiting for authorisation…
267
+
268
+ <div id="gh-device-step-pat" class="w-full flex flex-col gap-3 hidden">
269
+ <p class="text-sm text-gray-500 dark:text-gray-400 text-center">Enter a GitHub Personal Access Token with <code class="font-mono text-xs bg-gray-100 dark:bg-gray-800 px-1 rounded">public_repo</code> scope.</p>
270
+ <input id="gh-pat-input" type="password" placeholder="ghp_…"
271
+ class="w-full px-3 py-2 text-sm bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white outline-none focus:border-blue-500 transition">
272
+ <a href="https://github.com/settings/tokens/new?scopes=public_repo&description=dot-illustrations+demo" target="_blank" class="text-xs text-blue-600 dark:text-blue-400 underline text-center">Create token on GitHub ↗</a>
273
+ <button id="gh-pat-submit" class="w-full px-4 py-2 bg-gray-900 dark:bg-gray-700 hover:bg-gray-800 dark:hover:bg-gray-600 text-white text-sm font-medium rounded-lg transition">Connect</button>
215
274
  </div>
275
+
276
+ <div id="gh-device-error" class="hidden text-xs text-red-600 dark:text-red-400 text-center"></div>
216
277
  </div>
217
- <div id="gh-device-step-pat" class="w-full flex flex-col gap-3 hidden">
218
- <p class="text-sm text-gray-600 dark:text-gray-400 text-center">Enter a GitHub Personal Access Token with <code class="font-mono text-xs">public_repo</code> scope.</p>
219
- <input id="gh-pat-input" type="password" placeholder="ghp_…" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
220
- <a href="https://github.com/settings/tokens/new?scopes=public_repo&description=dot-illustrations+demo+upload" target="_blank" class="text-xs text-blue-600 dark:text-blue-400 underline text-center">Create a token on GitHub ↗</a>
221
- <button id="gh-pat-submit" class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition">Connect with token</button>
222
- </div>
223
- <div id="gh-device-error" class="hidden mt-2 text-sm text-red-600 dark:text-red-400 text-center"></div>
224
278
  </div>
225
279
  </div>
226
280
 
227
281
  </body>
228
- </html>
282
+ </html>
@@ -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,91 @@ 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
+ let uploadPanelOpen = false;
122
+ document.getElementById('upload-new-btn')?.addEventListener('click', () => {
123
+ uploadPanelOpen = !uploadPanelOpen;
124
+ const panel = document.getElementById('upload-panel');
125
+ panel.classList.toggle('hidden', !uploadPanelOpen);
126
+ if (uploadPanelOpen) {
127
+ panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
128
+ updateUploadAuthUI();
129
+ }
130
+ const btn = document.getElementById('upload-new-btn');
131
+ btn.classList.toggle('bg-blue-700', uploadPanelOpen);
132
+ btn.classList.toggle('ring-2', uploadPanelOpen);
133
+ btn.classList.toggle('ring-blue-400', uploadPanelOpen);
134
+ });
135
+
136
+ // ── Section switcher (Illustrations | Integrations) — pill style ──────────
137
+ function updateSectionUI(section) {
138
+ document.querySelectorAll('.section-btn, .floating-section-btn').forEach(btn => {
139
+ const active = btn.dataset.section === section;
140
+ // Active pill: white card on gray background
141
+ btn.classList.toggle('bg-white', active);
142
+ btn.classList.toggle('dark:bg-gray-700', active);
143
+ btn.classList.toggle('text-gray-900', active);
144
+ btn.classList.toggle('dark:text-white', active);
145
+ btn.classList.toggle('shadow-sm', active);
146
+ // Inactive: transparent
147
+ btn.classList.toggle('text-gray-500', !active);
148
+ btn.classList.toggle('dark:text-gray-400', !active);
129
149
  });
130
150
  }
131
151
 
132
- function setTab(tab) {
133
- currentTab = tab;
152
+ function setSection(section) {
153
+ currentSection = section;
134
154
  alphabetFilter = '';
135
- updateTabUI(tab);
155
+ updateSectionUI(section);
136
156
 
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');
157
+ const grid = document.getElementById('illustration-list');
158
+ const alphaStrip = document.getElementById('alphabet-strip');
159
+ const illusSubTabs = document.getElementById('illus-sub-tabs');
160
+ const floatingTabs = document.getElementById('floating-tabs');
161
+ const resultCount = document.getElementById('result-count');
141
162
 
142
- if (tab === 'upload') {
143
- uploadPanel.classList.remove('hidden');
144
- grid.classList.add('hidden');
163
+ if (section === 'integrations') {
164
+ grid.classList.remove('hidden');
145
165
  alphaStrip.classList.add('hidden');
146
- integNote.classList.add('hidden');
147
- document.getElementById('result-count').textContent = '';
148
- updateUploadAuthUI();
166
+ illusSubTabs.classList.add('hidden');
167
+ if (floatingTabs) floatingTabs.classList.add('hidden');
168
+ renderIllustrations();
149
169
  } else {
150
- uploadPanel.classList.add('hidden');
151
170
  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');
171
+ illusSubTabs.classList.remove('hidden');
172
+ if (floatingTabs) floatingTabs.classList.remove('hidden');
156
173
  renderAlphabetStrip();
157
174
  renderIllustrations();
158
175
  }
159
176
  }
160
177
 
178
+ document.querySelectorAll('.section-btn, .floating-section-btn').forEach(btn => {
179
+ btn.addEventListener('click', () => setSection(btn.dataset.section));
180
+ });
181
+
182
+ // ── Illustration sub-tabs — pill style ───────────────────────────────────
183
+ function updateTabUI(tab) {
184
+ document.querySelectorAll('.tab-btn').forEach(t => {
185
+ const active = t.dataset.tab === tab;
186
+ t.classList.toggle('bg-white', active);
187
+ t.classList.toggle('dark:bg-gray-700', active);
188
+ t.classList.toggle('text-gray-900', active);
189
+ t.classList.toggle('dark:text-white', active);
190
+ t.classList.toggle('shadow-sm', active);
191
+ t.classList.toggle('text-gray-500', !active);
192
+ t.classList.toggle('dark:text-gray-400', !active);
193
+ t.setAttribute('aria-selected', active ? 'true' : 'false');
194
+ });
195
+ }
196
+
197
+ function setTab(tab) {
198
+ currentTab = tab;
199
+ alphabetFilter = '';
200
+ updateTabUI(tab);
201
+ renderAlphabetStrip();
202
+ renderIllustrations();
203
+ }
204
+
161
205
  function wireTabs() {
162
206
  document.querySelectorAll('.tab-btn').forEach(tab => {
163
207
  tab.addEventListener('click', function() { setTab(this.dataset.tab); });
@@ -368,14 +412,14 @@ document.addEventListener('DOMContentLoaded', () => {
368
412
  const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
369
413
 
370
414
  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>
415
+ <button class="alpha-btn px-2 py-1 text-xs rounded-lg font-mono font-medium ${alphabetFilter === '' ? 'bg-blue-600 text-white shadow-sm' : 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border border-gray-200 dark:border-gray-700 hover:border-blue-400 hover:text-blue-600 dark:hover:text-blue-400'} transition" data-letter="">All</button>
372
416
  ${letters.map(l => `
373
- <button class="alpha-btn px-2 py-0.5 text-xs rounded font-mono ${
417
+ <button class="alpha-btn px-2 py-1 text-xs rounded-lg font-mono font-medium ${
374
418
  available.has(l)
375
419
  ? 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'
420
+ ? 'bg-blue-600 text-white shadow-sm'
421
+ : 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border border-gray-200 dark:border-gray-700 hover:border-blue-400 hover:text-blue-600 dark:hover:text-blue-400 transition'
422
+ : 'opacity-25 cursor-default text-gray-400 dark:text-gray-600'
379
423
  }" data-letter="${l}" ${available.has(l) ? '' : 'disabled'}>${l}</button>
380
424
  `).join('')}
381
425
  `;
@@ -391,11 +435,15 @@ document.addEventListener('DOMContentLoaded', () => {
391
435
  function getCurrentPool(filterOverride) {
392
436
  const f = filterOverride !== undefined ? filterOverride : alphabetFilter;
393
437
  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));
438
+ if (currentSection === 'integrations') {
439
+ base = integrationsList;
440
+ } else {
441
+ // illustrations section
442
+ if (currentTab === 'all') base = illustrations;
443
+ else if (currentTab === 'global') base = globalList;
444
+ else if (currentTab === 'dashboards') base = dashboardsList;
445
+ else if (currentTab === 'favourites') base = illustrations.filter(id => getFavourites().includes(id));
446
+ }
399
447
  if (f) base = base.filter(id => id[0].toUpperCase() === f);
400
448
  return base;
401
449
  }
@@ -417,8 +465,7 @@ document.addEventListener('DOMContentLoaded', () => {
417
465
  return;
418
466
  }
419
467
 
420
- const isIntegrations = currentTab === 'integrations' ||
421
- (currentTab === 'favourites' && toShow.every(id => integrationsList.includes(id)));
468
+ const isIntegrations = currentSection === 'integrations';
422
469
  const favs = getFavourites();
423
470
  const themeClass = root.classList.contains('dark') ? 'dark' : 'light';
424
471
 
@@ -427,8 +474,8 @@ document.addEventListener('DOMContentLoaded', () => {
427
474
  const isFav = favs.includes(item);
428
475
 
429
476
  if (isInteg) {
430
- // Integration card (single SVG, dot-integration wrapper)
431
- const codeString = `<span class="dot-integration"><img class="${item}"/></span>`;
477
+ // Integration card use direct <img src> so the image always loads
478
+ const htmlCode = `<span class="dot-integration"><img class="${item}"/></span>`;
432
479
  const div = document.createElement('div');
433
480
  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
481
  div.dataset.itemId = item;
@@ -438,7 +485,7 @@ document.addEventListener('DOMContentLoaded', () => {
438
485
  <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>
439
486
  </button>
440
487
  <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>
488
+ <img src="../integrations/${item}.svg" alt="${item}" class="w-28 h-28 object-contain" loading="lazy" />
442
489
  <span class="iconID block text-center text-base font-medium text-gray-800 dark:text-gray-200 my-2">${item}</span>
443
490
  </div>
444
491
  <div class="button-group flex flex-row items-center justify-center gap-2 mt-2">
@@ -449,7 +496,7 @@ document.addEventListener('DOMContentLoaded', () => {
449
496
  Copy Code<span class="tooltiptext left-1/2 -translate-x-1/2 bottom-10 shadow-md">Copy Code</span>
450
497
  </button>
451
498
  </div>
452
- <pre class="codeBlock"><code class="codeBlock">${codeString}</code></pre>
499
+ <pre class="codeBlock"><code class="codeBlock">${htmlCode}</code></pre>
453
500
  `;
454
501
  list.appendChild(div);
455
502
  } else {
@@ -516,7 +563,6 @@ document.addEventListener('DOMContentLoaded', () => {
516
563
  badge.classList.remove('hidden');
517
564
  badge.style.display = 'flex';
518
565
  name.textContent = `@${user.login}`;
519
- localStorage.setItem('gh_illus_user', JSON.stringify(user));
520
566
  } else {
521
567
  btn.classList.remove('hidden');
522
568
  badge.style.display = 'none';
@@ -591,11 +637,10 @@ document.addEventListener('DOMContentLoaded', () => {
591
637
  }
592
638
  });
593
639
 
594
- // Restore session
595
- const savedUser = localStorage.getItem('gh_illus_user');
596
- if (window.GitHubUpload?.getToken?.() && savedUser) {
597
- try { updateAuthBadge(JSON.parse(savedUser)); } catch {}
598
- }
640
+ // Auto-connect via local proxy (node demo/local-proxy.js) — no token input needed
641
+ window.GitHubUpload?.tryLocalProxy?.().then(user => {
642
+ if (user) { updateAuthBadge(user); updateUploadAuthUI(); }
643
+ });
599
644
 
600
645
  // ── Upload panel ─────────────────────────────────────────────────────────
601
646
  const uploadBufs = { illusLight: null, illusDark: null, integSvg: null };
@@ -683,8 +728,11 @@ document.addEventListener('DOMContentLoaded', () => {
683
728
  document.getElementById('upload-illus-btn')?.addEventListener('click', handleIllusUpload);
684
729
  document.getElementById('upload-integ-btn')?.addEventListener('click', handleIntegUpload);
685
730
 
686
- // Set initial tab state to 'all' and update UI (this will also render illustrations)
687
- setTab('all');
731
+ // Initial render
732
+ updateSectionUI('illustrations');
733
+ updateTabUI('all');
734
+ renderAlphabetStrip();
735
+ renderIllustrations();
688
736
 
689
737
  // --- FLOATING BAR LOGIC ---
690
738
  const floatingBar = document.getElementById('floating-bar');
@@ -714,13 +762,12 @@ function fuzzyMatch(str, query) {
714
762
 
715
763
  function getSuggestions(query) {
716
764
  if (!query) return [];
717
- // Use current tab's list
718
765
  let pool = [];
719
- if (currentTab === 'all') pool = illustrations;
720
- else if (currentTab === 'global') pool = globalList;
721
- else if (currentTab === 'dashboards') pool = dashboardsList;
722
- else if (currentTab === 'integrations') pool = integrationsList;
723
- else if (currentTab === 'favourites') pool = getFavourites();
766
+ if (currentSection === 'integrations') pool = integrationsList;
767
+ else if (currentTab === 'all') pool = illustrations;
768
+ else if (currentTab === 'global') pool = globalList;
769
+ else if (currentTab === 'dashboards') pool = dashboardsList;
770
+ else if (currentTab === 'favourites') pool = illustrations.filter(id => getFavourites().includes(id));
724
771
  // Fuzzy match, sort by closeness (shorter index of first match, then length)
725
772
  let scored = pool
726
773
  .map(id => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digital-ai/dot-illustrations",
3
- "version": "2.0.35",
3
+ "version": "2.0.37",
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": {