@digital-ai/dot-illustrations 2.0.33 → 2.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +21 -10
- package/demo/demo.css +27 -0
- package/demo/github-upload.js +286 -0
- package/demo/index.html +158 -19
- package/demo/script.js +390 -120
- package/illustrations/dark/global/remote-cloud.svg +37 -0
- package/illustrations/light/global/remote-cloud.svg +37 -0
- package/index.css +8 -0
- package/package.json +1 -1
|
@@ -6,6 +6,12 @@ on:
|
|
|
6
6
|
branches:
|
|
7
7
|
- main
|
|
8
8
|
|
|
9
|
+
# OIDC token for npm Trusted Publishers (no NPM_TOKEN secret needed).
|
|
10
|
+
# contents: write is required for the version-bump commit and release creation.
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # Required for npm OIDC Trusted Publishing
|
|
13
|
+
contents: write # Required for version bump commit + GitHub release
|
|
14
|
+
|
|
9
15
|
jobs:
|
|
10
16
|
# https://github.com/marketplace/actions/automated-version-bump
|
|
11
17
|
bump-version:
|
|
@@ -19,7 +25,7 @@ jobs:
|
|
|
19
25
|
uses: actions/checkout@v4
|
|
20
26
|
|
|
21
27
|
- name: Setup Node.js
|
|
22
|
-
uses: actions/setup-node@
|
|
28
|
+
uses: actions/setup-node@v4
|
|
23
29
|
with:
|
|
24
30
|
node-version: 20
|
|
25
31
|
|
|
@@ -64,7 +70,7 @@ jobs:
|
|
|
64
70
|
|
|
65
71
|
# https://github.com/marketplace/actions/npm-publish
|
|
66
72
|
npm-publish:
|
|
67
|
-
name: Publish
|
|
73
|
+
name: Publish to npm (OIDC Trusted Publisher)
|
|
68
74
|
runs-on: ubuntu-latest
|
|
69
75
|
needs: [bump-version, create-release]
|
|
70
76
|
if: needs.bump-version.outputs.new-version != ''
|
|
@@ -73,14 +79,19 @@ jobs:
|
|
|
73
79
|
- name: Checkout code
|
|
74
80
|
uses: actions/checkout@v4
|
|
75
81
|
with:
|
|
76
|
-
fetch-depth: 0
|
|
82
|
+
fetch-depth: 0
|
|
77
83
|
ref: 'main'
|
|
78
84
|
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
# registry-url wires the OIDC token into .npmrc automatically —
|
|
86
|
+
# no NPM_TOKEN secret required.
|
|
87
|
+
- name: Setup Node.js
|
|
88
|
+
uses: actions/setup-node@v4
|
|
82
89
|
with:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
node-version: 20
|
|
91
|
+
registry-url: 'https://registry.npmjs.org'
|
|
92
|
+
|
|
93
|
+
- name: Update npm (≥ 11.5.1 required for OIDC)
|
|
94
|
+
run: npm install -g npm@latest
|
|
95
|
+
|
|
96
|
+
- name: Publish to npm
|
|
97
|
+
run: npm publish --access public
|
package/demo/demo.css
CHANGED
|
@@ -179,3 +179,30 @@ body.dark-mode .dot-illustration img.dark {
|
|
|
179
179
|
height: 140px;
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
|
+
|
|
183
|
+
/* ── Alphabet strip ─────────────────────────────────────────────────────── */
|
|
184
|
+
#alphabet-strip {
|
|
185
|
+
display: flex;
|
|
186
|
+
flex-wrap: wrap;
|
|
187
|
+
justify-content: center;
|
|
188
|
+
gap: 0.25rem;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* ── Tab nav horizontal scroll ──────────────────────────────────────────── */
|
|
192
|
+
#main-tabs {
|
|
193
|
+
overflow-x: auto;
|
|
194
|
+
scrollbar-width: none;
|
|
195
|
+
}
|
|
196
|
+
#main-tabs::-webkit-scrollbar { display: none; }
|
|
197
|
+
#floating-tabs { scrollbar-width: none; }
|
|
198
|
+
#floating-tabs::-webkit-scrollbar { display: none; }
|
|
199
|
+
|
|
200
|
+
/* ── GitHub user badge flex ─────────────────────────────────────────────── */
|
|
201
|
+
#gh-user-badge[style*="flex"] { display: flex !important; }
|
|
202
|
+
|
|
203
|
+
/* ── Integration card image sizing ─────────────────────────────────────── */
|
|
204
|
+
.dot-integration img {
|
|
205
|
+
width: 100%;
|
|
206
|
+
height: 100%;
|
|
207
|
+
object-fit: contain;
|
|
208
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Upload Helper — dot-illustrations
|
|
3
|
+
* OAuth Device Flow + REST API for illustrations and integrations.
|
|
4
|
+
*
|
|
5
|
+
* Setup:
|
|
6
|
+
* 1. Register a GitHub OAuth App at https://github.com/settings/developers
|
|
7
|
+
* - Homepage: https://digital-ai.github.io/dot-illustrations/demo/
|
|
8
|
+
* - Check "Enable Device Flow" — no callback URL needed
|
|
9
|
+
* 2. Set GITHUB_OAUTH_CLIENT_ID to your Client ID
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const GITHUB_UPLOAD_CONFIG = {
|
|
13
|
+
CLIENT_ID: 'YOUR_GITHUB_OAUTH_CLIENT_ID',
|
|
14
|
+
REPO_OWNER: 'digital-ai',
|
|
15
|
+
REPO_NAME: 'dot-illustrations',
|
|
16
|
+
BASE_BRANCH: 'main',
|
|
17
|
+
SCOPES: 'public_repo',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// ─── In-memory token (never persisted — cleared when page closes) ────────────
|
|
21
|
+
|
|
22
|
+
let _ghToken = null;
|
|
23
|
+
let _ghUser = null;
|
|
24
|
+
|
|
25
|
+
function ghGetToken() { return _ghToken; }
|
|
26
|
+
function ghSetToken(t) { _ghToken = t; }
|
|
27
|
+
function ghClearToken() { _ghToken = null; _ghUser = null; }
|
|
28
|
+
|
|
29
|
+
// ─── GitHub API helpers ──────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
async function ghApi(path, opts = {}) {
|
|
32
|
+
const token = ghGetToken();
|
|
33
|
+
const resp = await fetch(`https://api.github.com${path}`, {
|
|
34
|
+
...opts,
|
|
35
|
+
headers: {
|
|
36
|
+
'Authorization': `Bearer ${token}`,
|
|
37
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
...(opts.headers || {}),
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const data = await resp.json();
|
|
43
|
+
if (!resp.ok) throw new Error(data.message || `GitHub API error ${resp.status}`);
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function ghGetUser() { return ghApi('/user'); }
|
|
48
|
+
|
|
49
|
+
async function ghGetMainSha() {
|
|
50
|
+
const d = await ghApi(
|
|
51
|
+
`/repos/${GITHUB_UPLOAD_CONFIG.REPO_OWNER}/${GITHUB_UPLOAD_CONFIG.REPO_NAME}/git/ref/heads/${GITHUB_UPLOAD_CONFIG.BASE_BRANCH}`
|
|
52
|
+
);
|
|
53
|
+
return d.object.sha;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function ghCreateBranch(name, sha) {
|
|
57
|
+
return ghApi(
|
|
58
|
+
`/repos/${GITHUB_UPLOAD_CONFIG.REPO_OWNER}/${GITHUB_UPLOAD_CONFIG.REPO_NAME}/git/refs`,
|
|
59
|
+
{ method: 'POST', body: JSON.stringify({ ref: `refs/heads/${name}`, sha }) }
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function ghGetFile(path, branch) {
|
|
64
|
+
try {
|
|
65
|
+
const d = await ghApi(
|
|
66
|
+
`/repos/${GITHUB_UPLOAD_CONFIG.REPO_OWNER}/${GITHUB_UPLOAD_CONFIG.REPO_NAME}/contents/${path}?ref=${branch}`
|
|
67
|
+
);
|
|
68
|
+
return { content: atob(d.content.replace(/\n/g, '')), sha: d.sha };
|
|
69
|
+
} catch { return null; }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function ghPutFile(path, textContent, message, branch, existingSha) {
|
|
73
|
+
const body = { message, branch, content: btoa(unescape(encodeURIComponent(textContent))) };
|
|
74
|
+
if (existingSha) body.sha = existingSha;
|
|
75
|
+
return ghApi(
|
|
76
|
+
`/repos/${GITHUB_UPLOAD_CONFIG.REPO_OWNER}/${GITHUB_UPLOAD_CONFIG.REPO_NAME}/contents/${path}`,
|
|
77
|
+
{ method: 'PUT', body: JSON.stringify(body) }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function ghPutBinaryFile(path, arrayBuffer, message, branch, existingSha) {
|
|
82
|
+
const bytes = new Uint8Array(arrayBuffer);
|
|
83
|
+
let bin = '';
|
|
84
|
+
for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
|
|
85
|
+
const body = { message, branch, content: btoa(bin) };
|
|
86
|
+
if (existingSha) body.sha = existingSha;
|
|
87
|
+
return ghApi(
|
|
88
|
+
`/repos/${GITHUB_UPLOAD_CONFIG.REPO_OWNER}/${GITHUB_UPLOAD_CONFIG.REPO_NAME}/contents/${path}`,
|
|
89
|
+
{ method: 'PUT', body: JSON.stringify(body) }
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function ghCreatePR(head, title, body) {
|
|
94
|
+
return ghApi(
|
|
95
|
+
`/repos/${GITHUB_UPLOAD_CONFIG.REPO_OWNER}/${GITHUB_UPLOAD_CONFIG.REPO_NAME}/pulls`,
|
|
96
|
+
{ method: 'POST', body: JSON.stringify({ title, body, head, base: GITHUB_UPLOAD_CONFIG.BASE_BRANCH }) }
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─── Device Flow ─────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
async function ghStartDeviceFlow() {
|
|
103
|
+
if (!GITHUB_UPLOAD_CONFIG.CLIENT_ID || GITHUB_UPLOAD_CONFIG.CLIENT_ID === 'YOUR_GITHUB_OAUTH_CLIENT_ID') {
|
|
104
|
+
throw new Error('NO_CLIENT_ID');
|
|
105
|
+
}
|
|
106
|
+
const resp = await fetch('https://github.com/login/device/code', {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
|
|
109
|
+
body: JSON.stringify({ client_id: GITHUB_UPLOAD_CONFIG.CLIENT_ID, scope: GITHUB_UPLOAD_CONFIG.SCOPES }),
|
|
110
|
+
});
|
|
111
|
+
if (!resp.ok) throw new Error('Device flow initiation failed');
|
|
112
|
+
return resp.json();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function ghPollForToken(deviceCode, intervalSec) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const timer = setInterval(async () => {
|
|
118
|
+
try {
|
|
119
|
+
const resp = await fetch('https://github.com/login/oauth/access_token', {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
|
|
122
|
+
body: JSON.stringify({
|
|
123
|
+
client_id: GITHUB_UPLOAD_CONFIG.CLIENT_ID,
|
|
124
|
+
device_code: deviceCode,
|
|
125
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
const d = await resp.json();
|
|
129
|
+
if (d.access_token) { clearInterval(timer); resolve(d.access_token); }
|
|
130
|
+
else if (d.error === 'expired_token' || d.error === 'access_denied') {
|
|
131
|
+
clearInterval(timer); reject(new Error(d.error_description || d.error));
|
|
132
|
+
}
|
|
133
|
+
} catch (e) { clearInterval(timer); reject(e); }
|
|
134
|
+
}, Math.max(intervalSec, 5) * 1000);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── High-level: create PR for a new illustration ────────────────────────────
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {string} id - illustration ID (no .svg)
|
|
142
|
+
* @param {string} category - 'global' | 'dashboards'
|
|
143
|
+
* @param {ArrayBuffer} lightBuf - light SVG
|
|
144
|
+
* @param {ArrayBuffer} darkBuf - dark SVG
|
|
145
|
+
* @returns {string} PR URL
|
|
146
|
+
*/
|
|
147
|
+
async function ghCreateIllustrationPR(id, category, lightBuf, darkBuf) {
|
|
148
|
+
const slug = id.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
149
|
+
const branch = `add-illustration-${slug}-${Date.now()}`;
|
|
150
|
+
|
|
151
|
+
const mainSha = await ghGetMainSha();
|
|
152
|
+
await ghCreateBranch(branch, mainSha);
|
|
153
|
+
|
|
154
|
+
// Upload light + dark SVGs
|
|
155
|
+
const lightPath = `illustrations/light/${category}/${id}.svg`;
|
|
156
|
+
const darkPath = `illustrations/dark/${category}/${id}.svg`;
|
|
157
|
+
|
|
158
|
+
const [existingLight, existingDark] = await Promise.all([
|
|
159
|
+
ghGetFile(lightPath, branch),
|
|
160
|
+
ghGetFile(darkPath, branch),
|
|
161
|
+
]);
|
|
162
|
+
await Promise.all([
|
|
163
|
+
ghPutBinaryFile(lightPath, lightBuf, `feat(illustrations): add ${id} (light)`, branch, existingLight?.sha),
|
|
164
|
+
ghPutBinaryFile(darkPath, darkBuf, `feat(illustrations): add ${id} (dark)`, branch, existingDark?.sha),
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
// Update index.css — add CSS rules
|
|
168
|
+
const cssFile = await ghGetFile('index.css', branch);
|
|
169
|
+
if (cssFile) {
|
|
170
|
+
const newRules = `\n.dot-illustration img.${id}.light {\n content: url('./illustrations/light/${category}/${id}.svg');\n}\n.dot-illustration img.${id}.dark {\n content: url('./illustrations/dark/${category}/${id}.svg');\n}\n`;
|
|
171
|
+
const updatedCss = cssFile.content + newRules;
|
|
172
|
+
await ghPutFile('index.css', updatedCss, `feat(css): add ${id} illustration rules`, branch, cssFile.sha);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Update demo/script.js — append to the correct array
|
|
176
|
+
const scriptFile = await ghGetFile('demo/script.js', branch);
|
|
177
|
+
if (scriptFile) {
|
|
178
|
+
const arrayName = category === 'dashboards' ? 'dashboardsList' : 'globalList';
|
|
179
|
+
const updated = appendToJsArray(scriptFile.content, arrayName, id);
|
|
180
|
+
if (updated !== scriptFile.content) {
|
|
181
|
+
await ghPutFile('demo/script.js', updated, `feat(demo): add ${id} to ${arrayName}`, branch, scriptFile.sha);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const pr = await ghCreatePR(
|
|
186
|
+
branch,
|
|
187
|
+
`feat(illustrations): add ${id} (${category})`,
|
|
188
|
+
`## New illustration: \`${id}\`
|
|
189
|
+
|
|
190
|
+
**Category:** ${category}
|
|
191
|
+
|
|
192
|
+
### Files added
|
|
193
|
+
- \`illustrations/light/${category}/${id}.svg\`
|
|
194
|
+
- \`illustrations/dark/${category}/${id}.svg\`
|
|
195
|
+
|
|
196
|
+
### Checklist for reviewer
|
|
197
|
+
- [ ] Light and dark variants are visually correct
|
|
198
|
+
- [ ] SVGs are optimised (no raster images, no external references)
|
|
199
|
+
- [ ] CSS rules added to \`index.css\`
|
|
200
|
+
- [ ] Demo list updated in \`demo/script.js\`
|
|
201
|
+
|
|
202
|
+
> This PR was created automatically via the dot-illustrations demo upload UI.`
|
|
203
|
+
);
|
|
204
|
+
return pr.html_url;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ─── High-level: create PR for a new integration ─────────────────────────────
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @param {string} id - integration ID (no .svg)
|
|
211
|
+
* @param {ArrayBuffer} svgBuf - SVG file
|
|
212
|
+
* @returns {string} PR URL
|
|
213
|
+
*/
|
|
214
|
+
async function ghCreateIntegrationPR(id, svgBuf) {
|
|
215
|
+
const slug = id.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
216
|
+
const branch = `add-integration-${slug}-${Date.now()}`;
|
|
217
|
+
|
|
218
|
+
const mainSha = await ghGetMainSha();
|
|
219
|
+
await ghCreateBranch(branch, mainSha);
|
|
220
|
+
|
|
221
|
+
// Upload SVG
|
|
222
|
+
const svgPath = `integrations/${id}.svg`;
|
|
223
|
+
const existingSvg = await ghGetFile(svgPath, branch);
|
|
224
|
+
await ghPutBinaryFile(svgPath, svgBuf, `feat(integrations): add ${id}`, branch, existingSvg?.sha);
|
|
225
|
+
|
|
226
|
+
// Update index.css
|
|
227
|
+
const cssFile = await ghGetFile('index.css', branch);
|
|
228
|
+
if (cssFile) {
|
|
229
|
+
const newRule = `\n.dot-integration img.${id} {\n content: url('./integrations/${id}.svg');\n}\n`;
|
|
230
|
+
await ghPutFile('index.css', cssFile.content + newRule, `feat(css): add ${id} integration rule`, branch, cssFile.sha);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Update demo/script.js — append to integrationsList
|
|
234
|
+
const scriptFile = await ghGetFile('demo/script.js', branch);
|
|
235
|
+
if (scriptFile) {
|
|
236
|
+
const updated = appendToJsArray(scriptFile.content, 'integrationsList', id);
|
|
237
|
+
if (updated !== scriptFile.content) {
|
|
238
|
+
await ghPutFile('demo/script.js', updated, `feat(demo): add ${id} to integrationsList`, branch, scriptFile.sha);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const pr = await ghCreatePR(
|
|
243
|
+
branch,
|
|
244
|
+
`feat(integrations): add ${id} logo`,
|
|
245
|
+
`## New integration: \`${id}\`
|
|
246
|
+
|
|
247
|
+
**File:** \`integrations/${id}.svg\`
|
|
248
|
+
|
|
249
|
+
### Checklist for reviewer
|
|
250
|
+
- [ ] SVG is optimised and legible at 48×48px
|
|
251
|
+
- [ ] Logo follows brand guidelines
|
|
252
|
+
- [ ] No raster images or external references
|
|
253
|
+
- [ ] CSS rule added to \`index.css\`
|
|
254
|
+
- [ ] Demo list updated in \`demo/script.js\`
|
|
255
|
+
|
|
256
|
+
> This PR was created automatically via the dot-illustrations demo upload UI.`
|
|
257
|
+
);
|
|
258
|
+
return pr.html_url;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ─── Utility: append an ID to a JS array in source text ──────────────────────
|
|
262
|
+
|
|
263
|
+
function appendToJsArray(source, arrayName, id) {
|
|
264
|
+
// Find the last entry in the named array and insert after it
|
|
265
|
+
const re = new RegExp(`(const ${arrayName} = \\[)([\\s\\S]*?)(\\s*\\];)`, 'm');
|
|
266
|
+
const match = source.match(re);
|
|
267
|
+
if (!match) return source;
|
|
268
|
+
const body = match[2];
|
|
269
|
+
const indent = (body.match(/\n(\s+)"/) || body.match(/\n(\s+)/) || ['', ' '])[1];
|
|
270
|
+
const newEntry = `\n${indent}"${id}",`;
|
|
271
|
+
return source.replace(re, `$1$2${newEntry}$3`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─── Export ──────────────────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
window.GitHubUpload = {
|
|
277
|
+
config: GITHUB_UPLOAD_CONFIG,
|
|
278
|
+
getToken: ghGetToken,
|
|
279
|
+
setToken: ghSetToken,
|
|
280
|
+
clearToken: ghClearToken,
|
|
281
|
+
getUser: ghGetUser,
|
|
282
|
+
startDeviceFlow: ghStartDeviceFlow,
|
|
283
|
+
pollForToken: ghPollForToken,
|
|
284
|
+
createIllustrationPR: ghCreateIllustrationPR,
|
|
285
|
+
createIntegrationPR: ghCreateIntegrationPR,
|
|
286
|
+
};
|
package/demo/index.html
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<script src="https://unpkg.com/@heroicons/vue@2.0.18/dist/heroicons.min.js"></script>
|
|
10
10
|
<link rel="stylesheet" href="../index.css" />
|
|
11
11
|
<link rel="stylesheet" href="./demo.css" />
|
|
12
|
+
<script src="./github-upload.js" defer></script>
|
|
12
13
|
<script src="script.js" defer></script>
|
|
13
14
|
<script>
|
|
14
15
|
// Tailwind dark mode config
|
|
@@ -19,23 +20,38 @@
|
|
|
19
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);">
|
|
20
21
|
<div class="flex flex-row items-center gap-2 w-full max-w-5xl">
|
|
21
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...">
|
|
22
|
-
<nav class="inline-flex rounded shadow overflow-hidden border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800" role="tablist">
|
|
23
|
-
<button class="floating-tab tab-btn 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>
|
|
24
|
-
<button class="floating-tab tab-btn 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>
|
|
25
|
-
<button class="floating-tab tab-btn 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>
|
|
26
|
-
<button class="floating-tab tab-btn 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-
|
|
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>
|
|
27
30
|
</nav>
|
|
28
31
|
</div>
|
|
29
32
|
</div>
|
|
30
33
|
<div class="container mx-auto px-4 py-8">
|
|
31
34
|
<div class="flex flex-col md:flex-row items-center justify-between mb-6 gap-4">
|
|
32
35
|
<h1 class="text-3xl md:text-5xl font-bold text-center md:text-left text-gray-900 dark:text-white">Dot Illustrations</h1>
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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>
|
|
41
|
+
Connect GitHub
|
|
42
|
+
</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>
|
|
45
|
+
<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>
|
|
47
|
+
</span>
|
|
48
|
+
</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>
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
39
55
|
</div>
|
|
40
56
|
<p class="text-center text-gray-700 dark:text-gray-300 mb-2">
|
|
41
57
|
For more details please see
|
|
@@ -47,16 +63,111 @@
|
|
|
47
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>
|
|
48
64
|
</div>
|
|
49
65
|
<!-- Tabs -->
|
|
50
|
-
<div class="mb-
|
|
51
|
-
<nav class="inline-flex rounded-lg shadow overflow-hidden border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800" role="tablist">
|
|
52
|
-
<button class="tab-btn px-
|
|
53
|
-
<button class="tab-btn px-
|
|
54
|
-
<button class="tab-btn px-
|
|
55
|
-
<button class="tab-btn px-
|
|
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>
|
|
56
74
|
</nav>
|
|
57
75
|
</div>
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
|
|
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>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
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>
|
|
87
|
+
|
|
88
|
+
<!-- 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 -->
|
|
91
|
+
</div>
|
|
92
|
+
|
|
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">
|
|
96
|
+
|
|
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" />
|
|
107
|
+
</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>
|
|
112
|
+
<option value="dashboards">Dashboards</option>
|
|
113
|
+
</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>
|
|
123
|
+
</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>
|
|
131
|
+
</div>
|
|
132
|
+
</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
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
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" />
|
|
151
|
+
</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" />
|
|
157
|
+
</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
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
60
171
|
</div>
|
|
61
172
|
</div>
|
|
62
173
|
<!-- Modal for illustration details -->
|
|
@@ -85,5 +196,33 @@
|
|
|
85
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>
|
|
86
197
|
</div>
|
|
87
198
|
</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">×</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>
|
|
211
|
+
</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…
|
|
215
|
+
</div>
|
|
216
|
+
</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
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
88
227
|
</body>
|
|
89
228
|
</html>
|