@styleframe/figma 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +440 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +831 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/code.js +3803 -0
- package/dist/plugin/ui.html +1045 -0
- package/manifest.json +16 -0
- package/package.json +61 -0
|
@@ -0,0 +1,1045 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Styleframe - Design Tokens Sync</title>
|
|
7
|
+
<script type="module" crossorigin>(function polyfill() {
|
|
8
|
+
const relList = document.createElement("link").relList;
|
|
9
|
+
if (relList && relList.supports && relList.supports("modulepreload")) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
for (const link of document.querySelectorAll('link[rel="modulepreload"]')) {
|
|
13
|
+
processPreload(link);
|
|
14
|
+
}
|
|
15
|
+
new MutationObserver((mutations) => {
|
|
16
|
+
for (const mutation of mutations) {
|
|
17
|
+
if (mutation.type !== "childList") {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
for (const node of mutation.addedNodes) {
|
|
21
|
+
if (node.tagName === "LINK" && node.rel === "modulepreload")
|
|
22
|
+
processPreload(node);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}).observe(document, { childList: true, subtree: true });
|
|
26
|
+
function getFetchOpts(link) {
|
|
27
|
+
const fetchOpts = {};
|
|
28
|
+
if (link.integrity) fetchOpts.integrity = link.integrity;
|
|
29
|
+
if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy;
|
|
30
|
+
if (link.crossOrigin === "use-credentials")
|
|
31
|
+
fetchOpts.credentials = "include";
|
|
32
|
+
else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit";
|
|
33
|
+
else fetchOpts.credentials = "same-origin";
|
|
34
|
+
return fetchOpts;
|
|
35
|
+
}
|
|
36
|
+
function processPreload(link) {
|
|
37
|
+
if (link.ep)
|
|
38
|
+
return;
|
|
39
|
+
link.ep = true;
|
|
40
|
+
const fetchOpts = getFetchOpts(link);
|
|
41
|
+
fetch(link.href, fetchOpts);
|
|
42
|
+
}
|
|
43
|
+
})();
|
|
44
|
+
function isDTCGFormat(data) {
|
|
45
|
+
if (typeof data !== "object" || data === null) return false;
|
|
46
|
+
const obj = data;
|
|
47
|
+
if ("collection" in obj && "modes" in obj && "variables" in obj) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if ("$modifiers" in obj) return true;
|
|
51
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
52
|
+
if (key.startsWith("$")) continue;
|
|
53
|
+
if (typeof value === "object" && value !== null) {
|
|
54
|
+
if ("$value" in value) return true;
|
|
55
|
+
for (const [, nested] of Object.entries(
|
|
56
|
+
value
|
|
57
|
+
)) {
|
|
58
|
+
if (typeof nested === "object" && nested !== null && "$value" in nested) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
function extractDTCGVariables(doc, path = "", inheritedType) {
|
|
67
|
+
const variables = [];
|
|
68
|
+
const currentType = doc.$type || inheritedType;
|
|
69
|
+
for (const [key, value] of Object.entries(doc)) {
|
|
70
|
+
if (key.startsWith("$")) continue;
|
|
71
|
+
if (typeof value !== "object" || value === null) continue;
|
|
72
|
+
const currentPath = path ? `${path}/${key}` : key;
|
|
73
|
+
if ("$value" in value) {
|
|
74
|
+
const token = value;
|
|
75
|
+
const type = token.$type || currentType || "string";
|
|
76
|
+
variables.push({
|
|
77
|
+
name: currentPath,
|
|
78
|
+
type: type.toUpperCase()
|
|
79
|
+
});
|
|
80
|
+
} else {
|
|
81
|
+
variables.push(
|
|
82
|
+
...extractDTCGVariables(
|
|
83
|
+
value,
|
|
84
|
+
currentPath,
|
|
85
|
+
currentType
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return variables;
|
|
91
|
+
}
|
|
92
|
+
const tabs = document.querySelectorAll(".tab");
|
|
93
|
+
const panels = document.querySelectorAll(".panel");
|
|
94
|
+
document.getElementById("import-panel");
|
|
95
|
+
document.getElementById("export-panel");
|
|
96
|
+
const fileDropZone = document.getElementById("file-drop-zone");
|
|
97
|
+
const fileInput = document.getElementById("file-input");
|
|
98
|
+
const fileSelected = document.getElementById("file-selected");
|
|
99
|
+
const fileName = document.getElementById("file-name");
|
|
100
|
+
const fileClearBtn = document.getElementById("file-clear-btn");
|
|
101
|
+
const importBtn = document.getElementById("import-btn");
|
|
102
|
+
const importPreview = document.getElementById("import-preview");
|
|
103
|
+
const importStatus = document.getElementById("import-status");
|
|
104
|
+
let loadedImportData = null;
|
|
105
|
+
const collectionSelect = document.getElementById(
|
|
106
|
+
"collection-select"
|
|
107
|
+
);
|
|
108
|
+
const refreshBtn = document.getElementById("refresh-btn");
|
|
109
|
+
const exportBtn = document.getElementById("export-btn");
|
|
110
|
+
const exportOutput = document.getElementById(
|
|
111
|
+
"export-output"
|
|
112
|
+
);
|
|
113
|
+
const copyBtn = document.getElementById("copy-btn");
|
|
114
|
+
const downloadBtn = document.getElementById(
|
|
115
|
+
"download-btn"
|
|
116
|
+
);
|
|
117
|
+
const exportStatus = document.getElementById("export-status");
|
|
118
|
+
let currentMode = "import";
|
|
119
|
+
let collections = [];
|
|
120
|
+
function init() {
|
|
121
|
+
tabs.forEach((tab) => {
|
|
122
|
+
tab.addEventListener("click", () => {
|
|
123
|
+
const mode = tab.getAttribute("data-tab");
|
|
124
|
+
switchTab(mode);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
fileDropZone.addEventListener("click", () => fileInput.click());
|
|
128
|
+
fileInput.addEventListener("change", handleFileSelect);
|
|
129
|
+
fileClearBtn.addEventListener("click", handleFileClear);
|
|
130
|
+
importBtn.addEventListener("click", handleImport);
|
|
131
|
+
fileDropZone.addEventListener("dragover", handleDragOver);
|
|
132
|
+
fileDropZone.addEventListener("dragleave", handleDragLeave);
|
|
133
|
+
fileDropZone.addEventListener("drop", handleDrop);
|
|
134
|
+
refreshBtn.addEventListener("click", requestCollections);
|
|
135
|
+
exportBtn.addEventListener("click", handleExport);
|
|
136
|
+
copyBtn.addEventListener("click", handleCopy);
|
|
137
|
+
downloadBtn.addEventListener("click", handleDownload);
|
|
138
|
+
requestCollections();
|
|
139
|
+
}
|
|
140
|
+
function switchTab(mode) {
|
|
141
|
+
currentMode = mode;
|
|
142
|
+
tabs.forEach((tab) => {
|
|
143
|
+
tab.classList.toggle("active", tab.getAttribute("data-tab") === mode);
|
|
144
|
+
});
|
|
145
|
+
panels.forEach((panel) => {
|
|
146
|
+
panel.classList.toggle("active", panel.id === `${mode}-panel`);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function handleDragOver(e) {
|
|
150
|
+
e.preventDefault();
|
|
151
|
+
e.stopPropagation();
|
|
152
|
+
fileDropZone.classList.add("drag-over");
|
|
153
|
+
}
|
|
154
|
+
function handleDragLeave(e) {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
e.stopPropagation();
|
|
157
|
+
fileDropZone.classList.remove("drag-over");
|
|
158
|
+
}
|
|
159
|
+
function handleDrop(e) {
|
|
160
|
+
var _a;
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
e.stopPropagation();
|
|
163
|
+
fileDropZone.classList.remove("drag-over");
|
|
164
|
+
const files = (_a = e.dataTransfer) == null ? void 0 : _a.files;
|
|
165
|
+
if (files && files.length > 0) {
|
|
166
|
+
const file = files[0];
|
|
167
|
+
if (file && (file.type === "application/json" || file.name.endsWith(".json"))) {
|
|
168
|
+
processFile(file);
|
|
169
|
+
} else {
|
|
170
|
+
showStatus(importStatus, "error", "Please select a JSON file");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function handleFileSelect() {
|
|
175
|
+
var _a;
|
|
176
|
+
const file = (_a = fileInput.files) == null ? void 0 : _a[0];
|
|
177
|
+
if (file) {
|
|
178
|
+
processFile(file);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function processFile(file) {
|
|
182
|
+
hideStatus(importStatus);
|
|
183
|
+
const reader = new FileReader();
|
|
184
|
+
reader.onload = (e) => {
|
|
185
|
+
var _a, _b;
|
|
186
|
+
try {
|
|
187
|
+
const content = (_a = e.target) == null ? void 0 : _a.result;
|
|
188
|
+
const data = JSON.parse(content);
|
|
189
|
+
const variables = getPreviewVariables(data);
|
|
190
|
+
loadedImportData = data;
|
|
191
|
+
(_b = fileDropZone.querySelector(".file-drop-content")) == null ? void 0 : _b.setAttribute("hidden", "");
|
|
192
|
+
fileSelected.removeAttribute("hidden");
|
|
193
|
+
fileName.textContent = file.name;
|
|
194
|
+
renderPreview(variables);
|
|
195
|
+
importBtn.disabled = false;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
const message = error instanceof Error ? error.message : "Invalid JSON";
|
|
198
|
+
importPreview.innerHTML = `<div class="preview-empty" style="color: var(--color-error)">${message}</div>`;
|
|
199
|
+
importBtn.disabled = true;
|
|
200
|
+
loadedImportData = null;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
reader.onerror = () => {
|
|
204
|
+
showStatus(importStatus, "error", "Failed to read file");
|
|
205
|
+
loadedImportData = null;
|
|
206
|
+
};
|
|
207
|
+
reader.readAsText(file);
|
|
208
|
+
}
|
|
209
|
+
function handleFileClear(e) {
|
|
210
|
+
var _a;
|
|
211
|
+
e.stopPropagation();
|
|
212
|
+
fileInput.value = "";
|
|
213
|
+
loadedImportData = null;
|
|
214
|
+
(_a = fileDropZone.querySelector(".file-drop-content")) == null ? void 0 : _a.removeAttribute("hidden");
|
|
215
|
+
fileSelected.setAttribute("hidden", "");
|
|
216
|
+
fileName.textContent = "";
|
|
217
|
+
importPreview.innerHTML = '<div class="preview-empty">Select a JSON file to preview variables</div>';
|
|
218
|
+
importBtn.disabled = true;
|
|
219
|
+
hideStatus(importStatus);
|
|
220
|
+
}
|
|
221
|
+
function getPreviewVariables(data) {
|
|
222
|
+
if (typeof data !== "object" || data === null) {
|
|
223
|
+
throw new Error("Invalid format: expected an object");
|
|
224
|
+
}
|
|
225
|
+
if (isDTCGFormat(data)) {
|
|
226
|
+
const variables = extractDTCGVariables(data);
|
|
227
|
+
if (variables.length === 0) {
|
|
228
|
+
throw new Error("No variables found in DTCG format");
|
|
229
|
+
}
|
|
230
|
+
return variables;
|
|
231
|
+
}
|
|
232
|
+
const obj = data;
|
|
233
|
+
if (!Array.isArray(obj.variables)) {
|
|
234
|
+
throw new Error("Invalid format: not a valid DTCG or legacy format");
|
|
235
|
+
}
|
|
236
|
+
return obj.variables.map((v) => ({
|
|
237
|
+
name: v.name,
|
|
238
|
+
type: v.type
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
function renderPreview(variables) {
|
|
242
|
+
if (variables.length === 0) {
|
|
243
|
+
importPreview.innerHTML = '<div class="preview-empty">No variables found in file</div>';
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const html = `
|
|
247
|
+
<div class="variable-list">
|
|
248
|
+
${variables.map(
|
|
249
|
+
(v) => `
|
|
250
|
+
<div class="variable-item">
|
|
251
|
+
<span class="variable-name">${v.name}</span>
|
|
252
|
+
<span class="variable-type ${v.type.toLowerCase()}">${v.type}</span>
|
|
253
|
+
</div>
|
|
254
|
+
`
|
|
255
|
+
).join("")}
|
|
256
|
+
</div>
|
|
257
|
+
`;
|
|
258
|
+
importPreview.innerHTML = html;
|
|
259
|
+
}
|
|
260
|
+
function handleImport() {
|
|
261
|
+
if (!loadedImportData) {
|
|
262
|
+
showStatus(importStatus, "error", "No file loaded");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
parent.postMessage(
|
|
266
|
+
{ pluginMessage: { type: "import", data: loadedImportData } },
|
|
267
|
+
"*"
|
|
268
|
+
);
|
|
269
|
+
importBtn.disabled = true;
|
|
270
|
+
}
|
|
271
|
+
function requestCollections() {
|
|
272
|
+
parent.postMessage({ pluginMessage: { type: "get-collections" } }, "*");
|
|
273
|
+
}
|
|
274
|
+
function handleExport() {
|
|
275
|
+
const collectionId = collectionSelect.value || void 0;
|
|
276
|
+
parent.postMessage({ pluginMessage: { type: "export", collectionId } }, "*");
|
|
277
|
+
exportBtn.disabled = true;
|
|
278
|
+
}
|
|
279
|
+
function handleCopy() {
|
|
280
|
+
const text = exportOutput.value;
|
|
281
|
+
if (!text) return;
|
|
282
|
+
const originalHTML = copyBtn.innerHTML;
|
|
283
|
+
const checkmarkIcon = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none">
|
|
284
|
+
<path d="M20 6L9 17l-5-5" stroke="#14ae5c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
285
|
+
</svg>`;
|
|
286
|
+
const copyToClipboard = async () => {
|
|
287
|
+
try {
|
|
288
|
+
await navigator.clipboard.writeText(text);
|
|
289
|
+
return true;
|
|
290
|
+
} catch {
|
|
291
|
+
const textarea = document.createElement("textarea");
|
|
292
|
+
textarea.value = text;
|
|
293
|
+
textarea.style.position = "fixed";
|
|
294
|
+
textarea.style.opacity = "0";
|
|
295
|
+
document.body.appendChild(textarea);
|
|
296
|
+
textarea.select();
|
|
297
|
+
const success = document.execCommand("copy");
|
|
298
|
+
document.body.removeChild(textarea);
|
|
299
|
+
return success;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
copyToClipboard().then((success) => {
|
|
303
|
+
if (success) {
|
|
304
|
+
copyBtn.innerHTML = checkmarkIcon;
|
|
305
|
+
copyBtn.title = "Copied to clipboard";
|
|
306
|
+
copyBtn.classList.add("copied");
|
|
307
|
+
setTimeout(() => {
|
|
308
|
+
copyBtn.innerHTML = originalHTML;
|
|
309
|
+
copyBtn.title = "Copy";
|
|
310
|
+
copyBtn.classList.remove("copied");
|
|
311
|
+
}, 2e3);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function handleDownload() {
|
|
316
|
+
const text = exportOutput.value;
|
|
317
|
+
if (!text) return;
|
|
318
|
+
const blob = new Blob([text], { type: "application/json" });
|
|
319
|
+
const url = URL.createObjectURL(blob);
|
|
320
|
+
const a = document.createElement("a");
|
|
321
|
+
a.href = url;
|
|
322
|
+
a.download = "tokens.json";
|
|
323
|
+
document.body.appendChild(a);
|
|
324
|
+
a.click();
|
|
325
|
+
document.body.removeChild(a);
|
|
326
|
+
URL.revokeObjectURL(url);
|
|
327
|
+
}
|
|
328
|
+
function updateCollections(newCollections) {
|
|
329
|
+
collections = newCollections;
|
|
330
|
+
if (collections.length === 0) {
|
|
331
|
+
collectionSelect.innerHTML = '<option value="">No collections found</option>';
|
|
332
|
+
exportBtn.disabled = true;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
collectionSelect.innerHTML = collections.map(
|
|
336
|
+
(c) => `<option value="${c.id}">${c.name} (${c.variableCount} variables)</option>`
|
|
337
|
+
).join("");
|
|
338
|
+
exportBtn.disabled = false;
|
|
339
|
+
}
|
|
340
|
+
function showStatus(element, type, message) {
|
|
341
|
+
element.textContent = message;
|
|
342
|
+
element.className = `status visible ${type}`;
|
|
343
|
+
}
|
|
344
|
+
function hideStatus(element) {
|
|
345
|
+
element.className = "status";
|
|
346
|
+
}
|
|
347
|
+
window.onmessage = (event) => {
|
|
348
|
+
var _a, _b;
|
|
349
|
+
const msg = event.data.pluginMessage;
|
|
350
|
+
if (!msg) return;
|
|
351
|
+
switch (msg.type) {
|
|
352
|
+
case "set-mode":
|
|
353
|
+
switchTab(msg.mode);
|
|
354
|
+
break;
|
|
355
|
+
case "collections":
|
|
356
|
+
updateCollections(msg.collections);
|
|
357
|
+
break;
|
|
358
|
+
case "import-complete":
|
|
359
|
+
showStatus(
|
|
360
|
+
importStatus,
|
|
361
|
+
"success",
|
|
362
|
+
`Successfully imported ${msg.result.variablesCreated} variables to "${msg.result.collection}"`
|
|
363
|
+
);
|
|
364
|
+
importBtn.disabled = false;
|
|
365
|
+
break;
|
|
366
|
+
case "export-complete": {
|
|
367
|
+
exportOutput.value = JSON.stringify(msg.result, null, 2);
|
|
368
|
+
const exportedVars = extractDTCGVariables(msg.result);
|
|
369
|
+
const collectionName = ((_b = (_a = msg.result.$extensions) == null ? void 0 : _a["dev.styleframe"]) == null ? void 0 : _b.collection) || "Collection";
|
|
370
|
+
showStatus(
|
|
371
|
+
exportStatus,
|
|
372
|
+
"success",
|
|
373
|
+
`Exported ${exportedVars.length} variables from "${collectionName}"`
|
|
374
|
+
);
|
|
375
|
+
exportBtn.disabled = false;
|
|
376
|
+
copyBtn.disabled = false;
|
|
377
|
+
downloadBtn.disabled = false;
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
case "error":
|
|
381
|
+
if (currentMode === "import") {
|
|
382
|
+
showStatus(importStatus, "error", msg.message);
|
|
383
|
+
importBtn.disabled = false;
|
|
384
|
+
} else {
|
|
385
|
+
showStatus(exportStatus, "error", msg.message);
|
|
386
|
+
exportBtn.disabled = false;
|
|
387
|
+
}
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
init();</script>
|
|
392
|
+
<style rel="stylesheet" crossorigin>/* Figma Plugin Design System */
|
|
393
|
+
:root {
|
|
394
|
+
/* Core colors - using Figma's CSS variables with proper fallbacks */
|
|
395
|
+
--color-bg: var(--figma-color-bg, #ffffff);
|
|
396
|
+
--color-bg-secondary: var(--figma-color-bg-secondary, #f5f5f5);
|
|
397
|
+
--color-bg-tertiary: var(--figma-color-bg-tertiary, #e5e5e5);
|
|
398
|
+
--color-bg-hover: var(--figma-color-bg-hover, #f0f0f0);
|
|
399
|
+
|
|
400
|
+
--color-text: var(--figma-color-text, #1e1e1e);
|
|
401
|
+
--color-text-secondary: var(--figma-color-text-secondary, #7c7c7c);
|
|
402
|
+
--color-text-tertiary: var(--figma-color-text-tertiary, #b3b3b3);
|
|
403
|
+
|
|
404
|
+
--color-border: var(--figma-color-border, #e5e5e5);
|
|
405
|
+
--color-border-hover: #d8d8d8;
|
|
406
|
+
--color-border-strong: var(--figma-color-border-strong, #c4c4c4);
|
|
407
|
+
|
|
408
|
+
--color-brand: var(--figma-color-bg-brand, #0c8ce9);
|
|
409
|
+
--color-brand-hover: var(--figma-color-bg-brand-hover, #0a7cd6);
|
|
410
|
+
--color-brand-secondary: var(--figma-color-bg-brand-secondary, #007be5);
|
|
411
|
+
--color-brand-tertiary: var(--figma-color-bg-brand-tertiary, #cce5ff);
|
|
412
|
+
|
|
413
|
+
--color-success: #14ae5c;
|
|
414
|
+
--color-success-bg: rgba(20, 174, 92, 0.08);
|
|
415
|
+
--color-error: #f24822;
|
|
416
|
+
--color-error-bg: rgba(242, 72, 34, 0.08);
|
|
417
|
+
|
|
418
|
+
/* Typography */
|
|
419
|
+
--font-family:
|
|
420
|
+
"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
421
|
+
--font-mono: "SF Mono", "Fira Code", "JetBrains Mono", Consolas, monospace;
|
|
422
|
+
|
|
423
|
+
/* Spacing */
|
|
424
|
+
--space-xs: 4px;
|
|
425
|
+
--space-sm: 8px;
|
|
426
|
+
--space-md: 12px;
|
|
427
|
+
--space-lg: 16px;
|
|
428
|
+
--space-xl: 24px;
|
|
429
|
+
|
|
430
|
+
/* Radii */
|
|
431
|
+
--radius-sm: 4px;
|
|
432
|
+
--radius-md: 6px;
|
|
433
|
+
--radius-lg: 8px;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
* {
|
|
437
|
+
box-sizing: border-box;
|
|
438
|
+
margin: 0;
|
|
439
|
+
padding: 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
[hidden] {
|
|
443
|
+
display: none !important;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
html,
|
|
447
|
+
body {
|
|
448
|
+
font-family: var(--font-family);
|
|
449
|
+
font-size: 11px;
|
|
450
|
+
line-height: 1.4;
|
|
451
|
+
color: var(--color-text);
|
|
452
|
+
background: var(--color-bg);
|
|
453
|
+
-webkit-font-smoothing: antialiased;
|
|
454
|
+
-moz-osx-font-smoothing: grayscale;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/* Layout */
|
|
458
|
+
.container {
|
|
459
|
+
display: flex;
|
|
460
|
+
flex-direction: column;
|
|
461
|
+
height: 100vh;
|
|
462
|
+
padding: var(--space-lg);
|
|
463
|
+
gap: var(--space-md);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/* Tabs */
|
|
467
|
+
.tabs {
|
|
468
|
+
display: flex;
|
|
469
|
+
gap: var(--space-xs);
|
|
470
|
+
padding: var(--space-xs);
|
|
471
|
+
background: var(--color-bg-secondary);
|
|
472
|
+
border-radius: var(--radius-md);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.tab {
|
|
476
|
+
flex: 1;
|
|
477
|
+
padding: var(--space-sm) var(--space-md);
|
|
478
|
+
border: none;
|
|
479
|
+
background: transparent;
|
|
480
|
+
cursor: pointer;
|
|
481
|
+
font-family: inherit;
|
|
482
|
+
font-size: 11px;
|
|
483
|
+
font-weight: 600;
|
|
484
|
+
color: var(--color-text-secondary);
|
|
485
|
+
border-radius: var(--radius-sm);
|
|
486
|
+
transition: all 0.15s ease;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.tab:hover:not(.active) {
|
|
490
|
+
color: var(--color-text);
|
|
491
|
+
background: var(--color-bg-hover);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.tab.active {
|
|
495
|
+
color: var(--color-text);
|
|
496
|
+
background: var(--color-bg);
|
|
497
|
+
box-shadow:
|
|
498
|
+
0 1px 2px rgba(0, 0, 0, 0.06),
|
|
499
|
+
0 1px 3px rgba(0, 0, 0, 0.1);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* Panels */
|
|
503
|
+
.panel {
|
|
504
|
+
display: none;
|
|
505
|
+
flex: 1;
|
|
506
|
+
flex-direction: column;
|
|
507
|
+
gap: var(--space-md);
|
|
508
|
+
overflow: hidden;
|
|
509
|
+
min-height: 0;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.panel.active {
|
|
513
|
+
display: flex;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/* Form Elements */
|
|
517
|
+
.form-group {
|
|
518
|
+
display: flex;
|
|
519
|
+
flex-direction: column;
|
|
520
|
+
gap: var(--space-xs);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
label {
|
|
524
|
+
font-weight: 600;
|
|
525
|
+
color: var(--color-text);
|
|
526
|
+
display: flex;
|
|
527
|
+
align-items: center;
|
|
528
|
+
gap: var(--space-xs);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
label code {
|
|
532
|
+
font-family: var(--font-mono);
|
|
533
|
+
font-size: 10px;
|
|
534
|
+
font-weight: 500;
|
|
535
|
+
padding: 2px 6px;
|
|
536
|
+
background: var(--color-bg-secondary);
|
|
537
|
+
border-radius: var(--radius-sm);
|
|
538
|
+
color: var(--color-text-secondary);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
input,
|
|
542
|
+
select {
|
|
543
|
+
width: 100%;
|
|
544
|
+
height: 32px;
|
|
545
|
+
padding: 0 var(--space-sm);
|
|
546
|
+
border: 1px solid var(--color-border);
|
|
547
|
+
border-radius: var(--radius-md);
|
|
548
|
+
font-family: inherit;
|
|
549
|
+
font-size: 11px;
|
|
550
|
+
background: var(--color-bg);
|
|
551
|
+
color: var(--color-text);
|
|
552
|
+
transition: border-color 0.15s ease;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
input:hover,
|
|
556
|
+
select:hover {
|
|
557
|
+
border-color: var(--color-border-hover);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
input:focus,
|
|
561
|
+
select:focus {
|
|
562
|
+
border-color: var(--color-brand);
|
|
563
|
+
outline: 1px solid var(--color-brand);
|
|
564
|
+
outline-offset: 0;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
textarea {
|
|
568
|
+
flex: 1;
|
|
569
|
+
min-height: 140px;
|
|
570
|
+
padding: var(--space-sm);
|
|
571
|
+
border: 1px solid var(--color-border);
|
|
572
|
+
border-radius: var(--radius-md);
|
|
573
|
+
font-family: var(--font-mono);
|
|
574
|
+
font-size: 10px;
|
|
575
|
+
line-height: 1.6;
|
|
576
|
+
background: var(--color-bg);
|
|
577
|
+
color: var(--color-text);
|
|
578
|
+
resize: none;
|
|
579
|
+
transition: border-color 0.15s ease;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
textarea:hover {
|
|
583
|
+
border-color: var(--color-border-hover);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
textarea:focus {
|
|
587
|
+
border-color: var(--color-brand);
|
|
588
|
+
outline: 1px solid var(--color-brand);
|
|
589
|
+
outline-offset: 0;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
textarea::placeholder {
|
|
593
|
+
color: var(--color-text-tertiary);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/* Buttons */
|
|
597
|
+
.btn {
|
|
598
|
+
display: inline-flex;
|
|
599
|
+
align-items: center;
|
|
600
|
+
justify-content: center;
|
|
601
|
+
gap: var(--space-xs);
|
|
602
|
+
height: 32px;
|
|
603
|
+
padding: 0 var(--space-md);
|
|
604
|
+
border: none;
|
|
605
|
+
border-radius: var(--radius-md);
|
|
606
|
+
font-family: inherit;
|
|
607
|
+
font-size: 11px;
|
|
608
|
+
font-weight: 600;
|
|
609
|
+
cursor: pointer;
|
|
610
|
+
transition: all 0.15s ease;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.btn-primary {
|
|
614
|
+
background: var(--color-brand);
|
|
615
|
+
color: #ffffff;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.btn-primary:hover:not(:disabled) {
|
|
619
|
+
background: var(--color-brand-hover);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.btn-primary:active:not(:disabled) {
|
|
623
|
+
background: var(--color-brand-secondary);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.btn-primary:disabled {
|
|
627
|
+
background: var(--color-brand);
|
|
628
|
+
opacity: 0.4;
|
|
629
|
+
cursor: not-allowed;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.btn-secondary {
|
|
633
|
+
background: var(--color-bg-secondary);
|
|
634
|
+
color: var(--color-text);
|
|
635
|
+
border: 1px solid var(--color-border);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.btn-secondary:hover:not(:disabled) {
|
|
639
|
+
background: var(--color-bg-tertiary);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.btn-secondary:disabled {
|
|
643
|
+
opacity: 0.4;
|
|
644
|
+
cursor: not-allowed;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/* Status Messages */
|
|
648
|
+
.status {
|
|
649
|
+
padding: var(--space-sm) var(--space-md);
|
|
650
|
+
border-radius: var(--radius-md);
|
|
651
|
+
font-size: 11px;
|
|
652
|
+
font-weight: 500;
|
|
653
|
+
display: none;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.status.visible {
|
|
657
|
+
display: flex;
|
|
658
|
+
align-items: center;
|
|
659
|
+
gap: var(--space-sm);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
.status.success {
|
|
663
|
+
background: var(--color-success-bg);
|
|
664
|
+
color: var(--color-success);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.status.success::before {
|
|
668
|
+
content: "✓";
|
|
669
|
+
font-weight: 700;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.status.error {
|
|
673
|
+
background: var(--color-error-bg);
|
|
674
|
+
color: var(--color-error);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.status.error::before {
|
|
678
|
+
content: "!";
|
|
679
|
+
font-weight: 700;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/* File Drop Zone */
|
|
683
|
+
.file-drop-zone {
|
|
684
|
+
display: flex;
|
|
685
|
+
flex-direction: column;
|
|
686
|
+
align-items: center;
|
|
687
|
+
justify-content: center;
|
|
688
|
+
padding: var(--space-xl);
|
|
689
|
+
border: 2px dashed var(--color-border);
|
|
690
|
+
border-radius: var(--radius-lg);
|
|
691
|
+
background: var(--color-bg-secondary);
|
|
692
|
+
cursor: pointer;
|
|
693
|
+
transition: all 0.15s ease;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.file-drop-zone:hover {
|
|
697
|
+
border-color: var(--color-brand);
|
|
698
|
+
background: var(--color-bg-hover);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.file-drop-zone.drag-over {
|
|
702
|
+
border-color: var(--color-brand);
|
|
703
|
+
background: var(--color-brand-tertiary);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.file-drop-content {
|
|
707
|
+
display: flex;
|
|
708
|
+
flex-direction: column;
|
|
709
|
+
align-items: center;
|
|
710
|
+
gap: var(--space-sm);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.file-icon {
|
|
714
|
+
color: var(--color-text-tertiary);
|
|
715
|
+
transition: color 0.15s ease;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.file-drop-zone:hover .file-icon {
|
|
719
|
+
color: var(--color-text-secondary);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.file-drop-zone.drag-over .file-icon {
|
|
723
|
+
color: var(--color-brand);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.file-drop-text {
|
|
727
|
+
font-size: 11px;
|
|
728
|
+
color: var(--color-text-secondary);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.file-browse-link {
|
|
732
|
+
color: var(--color-brand);
|
|
733
|
+
text-decoration: underline;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.file-drop-hint {
|
|
737
|
+
font-size: 10px;
|
|
738
|
+
color: var(--color-text-tertiary);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.file-selected {
|
|
742
|
+
display: flex;
|
|
743
|
+
align-items: center;
|
|
744
|
+
gap: var(--space-sm);
|
|
745
|
+
padding: var(--space-sm) var(--space-md);
|
|
746
|
+
background: var(--color-bg);
|
|
747
|
+
border-radius: var(--radius-md);
|
|
748
|
+
border: 1px solid var(--color-border);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.file-check-icon {
|
|
752
|
+
color: var(--color-success);
|
|
753
|
+
flex-shrink: 0;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.file-name {
|
|
757
|
+
flex: 1;
|
|
758
|
+
font-size: 11px;
|
|
759
|
+
font-weight: 500;
|
|
760
|
+
color: var(--color-text);
|
|
761
|
+
overflow: hidden;
|
|
762
|
+
text-overflow: ellipsis;
|
|
763
|
+
white-space: nowrap;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.file-clear-btn {
|
|
767
|
+
width: 20px;
|
|
768
|
+
height: 20px;
|
|
769
|
+
padding: 0;
|
|
770
|
+
border: none;
|
|
771
|
+
background: transparent;
|
|
772
|
+
border-radius: var(--radius-sm);
|
|
773
|
+
cursor: pointer;
|
|
774
|
+
display: flex;
|
|
775
|
+
align-items: center;
|
|
776
|
+
justify-content: center;
|
|
777
|
+
color: var(--color-text-tertiary);
|
|
778
|
+
transition: all 0.15s ease;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
.file-clear-btn:hover {
|
|
782
|
+
background: var(--color-bg-tertiary);
|
|
783
|
+
color: var(--color-text);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/* Preview Area */
|
|
787
|
+
.preview {
|
|
788
|
+
flex: 1;
|
|
789
|
+
overflow: auto;
|
|
790
|
+
background: var(--color-bg-secondary);
|
|
791
|
+
border-radius: var(--radius-md);
|
|
792
|
+
border: 1px solid var(--color-border);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
.preview-empty {
|
|
796
|
+
color: var(--color-text-tertiary);
|
|
797
|
+
text-align: center;
|
|
798
|
+
padding: var(--space-xl);
|
|
799
|
+
font-size: 11px;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
.variable-list {
|
|
803
|
+
display: flex;
|
|
804
|
+
flex-direction: column;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
.variable-item {
|
|
808
|
+
display: flex;
|
|
809
|
+
align-items: center;
|
|
810
|
+
gap: var(--space-sm);
|
|
811
|
+
padding: var(--space-sm) var(--space-md);
|
|
812
|
+
border-bottom: 1px solid var(--color-border);
|
|
813
|
+
transition: background 0.1s ease;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.variable-item:last-child {
|
|
817
|
+
border-bottom: none;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
.variable-item:hover {
|
|
821
|
+
background: var(--color-bg-hover);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
.variable-name {
|
|
825
|
+
flex: 1;
|
|
826
|
+
font-family: var(--font-mono);
|
|
827
|
+
font-size: 11px;
|
|
828
|
+
font-weight: 500;
|
|
829
|
+
color: var(--color-text);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.variable-type {
|
|
833
|
+
padding: 2px 6px;
|
|
834
|
+
border-radius: var(--radius-sm);
|
|
835
|
+
font-size: 9px;
|
|
836
|
+
font-weight: 600;
|
|
837
|
+
text-transform: uppercase;
|
|
838
|
+
letter-spacing: 0.3px;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.variable-type.color {
|
|
842
|
+
background: #f3e8ff;
|
|
843
|
+
color: #7c3aed;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.variable-type.float,
|
|
847
|
+
.variable-type.dimension,
|
|
848
|
+
.variable-type.number {
|
|
849
|
+
background: #dbeafe;
|
|
850
|
+
color: #2563eb;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
.variable-type.string {
|
|
854
|
+
background: #dcfce7;
|
|
855
|
+
color: #16a34a;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.variable-type.boolean {
|
|
859
|
+
background: #ffedd5;
|
|
860
|
+
color: #ea580c;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/* Collection Select */
|
|
864
|
+
.collection-select {
|
|
865
|
+
display: flex;
|
|
866
|
+
align-items: center;
|
|
867
|
+
gap: var(--space-sm);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
.collection-select select {
|
|
871
|
+
flex: 1;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.refresh-btn {
|
|
875
|
+
width: 32px;
|
|
876
|
+
height: 32px;
|
|
877
|
+
padding: 0;
|
|
878
|
+
border: 1px solid var(--color-border);
|
|
879
|
+
background: var(--color-bg);
|
|
880
|
+
border-radius: var(--radius-md);
|
|
881
|
+
cursor: pointer;
|
|
882
|
+
display: flex;
|
|
883
|
+
align-items: center;
|
|
884
|
+
justify-content: center;
|
|
885
|
+
font-size: 14px;
|
|
886
|
+
color: var(--color-text-secondary);
|
|
887
|
+
transition: all 0.15s ease;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.refresh-btn:hover {
|
|
891
|
+
background: var(--color-bg-secondary);
|
|
892
|
+
color: var(--color-text);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.refresh-btn:active {
|
|
896
|
+
background: var(--color-bg-tertiary);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/* Output Wrapper */
|
|
900
|
+
.output-wrapper {
|
|
901
|
+
position: relative;
|
|
902
|
+
flex: 1;
|
|
903
|
+
display: flex;
|
|
904
|
+
flex-direction: column;
|
|
905
|
+
gap: var(--space-xs);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.output-actions {
|
|
909
|
+
position: absolute;
|
|
910
|
+
top: 28px;
|
|
911
|
+
right: var(--space-sm);
|
|
912
|
+
display: flex;
|
|
913
|
+
gap: var(--space-xs);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
.output-actions .btn-icon {
|
|
917
|
+
width: 28px;
|
|
918
|
+
height: 28px;
|
|
919
|
+
padding: 0;
|
|
920
|
+
display: flex;
|
|
921
|
+
align-items: center;
|
|
922
|
+
justify-content: center;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
.output-actions .btn-icon svg {
|
|
926
|
+
flex-shrink: 0;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
.output-actions .btn-icon.copied {
|
|
930
|
+
pointer-events: none;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/* Scrollbar Styling */
|
|
934
|
+
::-webkit-scrollbar {
|
|
935
|
+
width: 8px;
|
|
936
|
+
height: 8px;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
::-webkit-scrollbar-track {
|
|
940
|
+
background: transparent;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
::-webkit-scrollbar-thumb {
|
|
944
|
+
background: var(--color-border-strong);
|
|
945
|
+
border-radius: 4px;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
::-webkit-scrollbar-thumb:hover {
|
|
949
|
+
background: var(--color-text-tertiary);
|
|
950
|
+
}</style>
|
|
951
|
+
</head>
|
|
952
|
+
<body>
|
|
953
|
+
<div class="container">
|
|
954
|
+
<div class="tabs">
|
|
955
|
+
<button class="tab active" data-tab="import">Import</button>
|
|
956
|
+
<button class="tab" data-tab="export">Export</button>
|
|
957
|
+
</div>
|
|
958
|
+
|
|
959
|
+
<!-- Import Panel -->
|
|
960
|
+
<div id="import-panel" class="panel active">
|
|
961
|
+
<div class="file-drop-zone" id="file-drop-zone">
|
|
962
|
+
<input type="file" id="file-input" accept=".json" hidden>
|
|
963
|
+
<div class="file-drop-content">
|
|
964
|
+
<svg class="file-icon" width="32" height="32" viewBox="0 0 24 24" fill="none">
|
|
965
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
966
|
+
<path d="M14 2v6h6M12 18v-6M9 15l3-3 3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
967
|
+
</svg>
|
|
968
|
+
<span class="file-drop-text">Drop JSON file here or <span class="file-browse-link">browse</span></span>
|
|
969
|
+
<span class="file-drop-hint">tokens.json from styleframe figma export</span>
|
|
970
|
+
</div>
|
|
971
|
+
<div class="file-selected" id="file-selected" hidden>
|
|
972
|
+
<svg class="file-check-icon" width="16" height="16" viewBox="0 0 24 24" fill="none">
|
|
973
|
+
<path d="M9 12l2 2 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
974
|
+
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="1.5"/>
|
|
975
|
+
</svg>
|
|
976
|
+
<span class="file-name" id="file-name"></span>
|
|
977
|
+
<button class="file-clear-btn" id="file-clear-btn" type="button">
|
|
978
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none">
|
|
979
|
+
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
980
|
+
</svg>
|
|
981
|
+
</button>
|
|
982
|
+
</div>
|
|
983
|
+
</div>
|
|
984
|
+
|
|
985
|
+
<div id="import-preview" class="preview">
|
|
986
|
+
<div class="preview-empty">Select a JSON file to preview variables</div>
|
|
987
|
+
</div>
|
|
988
|
+
|
|
989
|
+
<div id="import-status" class="status"></div>
|
|
990
|
+
|
|
991
|
+
<button id="import-btn" class="btn btn-primary" disabled>
|
|
992
|
+
Import Variables
|
|
993
|
+
</button>
|
|
994
|
+
</div>
|
|
995
|
+
|
|
996
|
+
<!-- Export Panel -->
|
|
997
|
+
<div id="export-panel" class="panel">
|
|
998
|
+
<div class="form-group">
|
|
999
|
+
<label for="collection-select">Collection</label>
|
|
1000
|
+
<div class="collection-select">
|
|
1001
|
+
<select id="collection-select">
|
|
1002
|
+
<option value="">Loading...</option>
|
|
1003
|
+
</select>
|
|
1004
|
+
<button id="refresh-btn" class="refresh-btn" title="Refresh">
|
|
1005
|
+
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
|
|
1006
|
+
<path d="M13.65 2.35a8 8 0 1 0 1.86 5.15h-2.02a6 6 0 1 1-1.28-3.71L10 6h6V0l-2.35 2.35z" fill="currentColor"/>
|
|
1007
|
+
</svg>
|
|
1008
|
+
</button>
|
|
1009
|
+
</div>
|
|
1010
|
+
</div>
|
|
1011
|
+
|
|
1012
|
+
<button id="export-btn" class="btn btn-primary" disabled>
|
|
1013
|
+
Export Variables
|
|
1014
|
+
</button>
|
|
1015
|
+
|
|
1016
|
+
<div class="output-wrapper">
|
|
1017
|
+
<label>Output</label>
|
|
1018
|
+
<textarea
|
|
1019
|
+
id="export-output"
|
|
1020
|
+
readonly
|
|
1021
|
+
placeholder="Click Export to generate DTCG JSON..."
|
|
1022
|
+
></textarea>
|
|
1023
|
+
<div class="output-actions">
|
|
1024
|
+
<button id="copy-btn" class="btn btn-secondary btn-icon" disabled title="Copy">
|
|
1025
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none">
|
|
1026
|
+
<rect x="9" y="9" width="13" height="13" rx="2" stroke="currentColor" stroke-width="2"/>
|
|
1027
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" stroke="currentColor" stroke-width="2"/>
|
|
1028
|
+
</svg>
|
|
1029
|
+
</button>
|
|
1030
|
+
<button id="download-btn" class="btn btn-secondary btn-icon" disabled title="Download">
|
|
1031
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none">
|
|
1032
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
1033
|
+
<polyline points="7 10 12 15 17 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
1034
|
+
<line x1="12" y1="15" x2="12" y2="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
1035
|
+
</svg>
|
|
1036
|
+
</button>
|
|
1037
|
+
</div>
|
|
1038
|
+
</div>
|
|
1039
|
+
|
|
1040
|
+
<div id="export-status" class="status"></div>
|
|
1041
|
+
</div>
|
|
1042
|
+
</div>
|
|
1043
|
+
|
|
1044
|
+
</body>
|
|
1045
|
+
</html>
|