Figma Design Specification Generator

Analyzes Figma designs and generates detailed specifications using AI

Size

25.9 KB

Version

1.0.1

Created

Oct 30, 2025

Updated

15 days ago

1// ==UserScript==
2// @name		Figma Design Specification Generator
3// @description		Analyzes Figma designs and generates detailed specifications using AI
4// @version		1.0.1
5// @match		https://*.figma.com/*
6// @icon		https://static.figma.com/app/icon/1/favicon.svg
7// @grant		GM.xmlhttpRequest
8// @grant		GM.getValue
9// @grant		GM.setValue
10// ==/UserScript==
11(function() {
12    'use strict';
13
14    console.log('Figma Design Specification Generator loaded');
15
16    // Debounce function to prevent excessive calls
17    function debounce(func, wait) {
18        let timeout;
19        return function executedFunction(...args) {
20            const later = () => {
21                clearTimeout(timeout);
22                func(...args);
23            };
24            clearTimeout(timeout);
25            timeout = setTimeout(later, wait);
26        };
27    }
28
29    // Add custom styles for the specification panel
30    function addStyles() {
31        const styles = `
32            .spec-gen-button {
33                position: fixed;
34                top: 16px;
35                right: 80px;
36                z-index: 10000;
37                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
38                color: white;
39                border: none;
40                padding: 10px 20px;
41                border-radius: 8px;
42                font-size: 14px;
43                font-weight: 600;
44                cursor: pointer;
45                box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
46                transition: all 0.3s ease;
47                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
48            }
49
50            .spec-gen-button:hover {
51                transform: translateY(-2px);
52                box-shadow: 0 6px 16px rgba(102, 126, 234, 0.5);
53            }
54
55            .spec-gen-button:active {
56                transform: translateY(0);
57            }
58
59            .spec-gen-button:disabled {
60                opacity: 0.6;
61                cursor: not-allowed;
62                transform: none;
63            }
64
65            .spec-panel {
66                position: fixed;
67                top: 0;
68                right: 0;
69                width: 500px;
70                height: 100vh;
71                background: #ffffff;
72                box-shadow: -4px 0 24px rgba(0, 0, 0, 0.15);
73                z-index: 9999;
74                display: flex;
75                flex-direction: column;
76                transform: translateX(100%);
77                transition: transform 0.3s ease;
78            }
79
80            .spec-panel.open {
81                transform: translateX(0);
82            }
83
84            .spec-panel-header {
85                padding: 20px 24px;
86                border-bottom: 1px solid #e5e5e5;
87                display: flex;
88                justify-content: space-between;
89                align-items: center;
90                background: #f8f9fa;
91            }
92
93            .spec-panel-title {
94                font-size: 18px;
95                font-weight: 700;
96                color: #1a1a1a;
97                margin: 0;
98                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
99            }
100
101            .spec-panel-close {
102                background: none;
103                border: none;
104                font-size: 24px;
105                cursor: pointer;
106                color: #666;
107                padding: 0;
108                width: 32px;
109                height: 32px;
110                display: flex;
111                align-items: center;
112                justify-content: center;
113                border-radius: 4px;
114                transition: background 0.2s;
115            }
116
117            .spec-panel-close:hover {
118                background: #e5e5e5;
119            }
120
121            .spec-panel-content {
122                flex: 1;
123                overflow-y: auto;
124                padding: 24px;
125            }
126
127            .spec-loading {
128                display: flex;
129                flex-direction: column;
130                align-items: center;
131                justify-content: center;
132                height: 100%;
133                color: #666;
134            }
135
136            .spec-spinner {
137                width: 48px;
138                height: 48px;
139                border: 4px solid #f3f3f3;
140                border-top: 4px solid #667eea;
141                border-radius: 50%;
142                animation: spin 1s linear infinite;
143                margin-bottom: 16px;
144            }
145
146            @keyframes spin {
147                0% { transform: rotate(0deg); }
148                100% { transform: rotate(360deg); }
149            }
150
151            .spec-section {
152                margin-bottom: 28px;
153            }
154
155            .spec-section-title {
156                font-size: 16px;
157                font-weight: 700;
158                color: #1a1a1a;
159                margin: 0 0 12px 0;
160                padding-bottom: 8px;
161                border-bottom: 2px solid #667eea;
162                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
163            }
164
165            .spec-section-content {
166                font-size: 14px;
167                line-height: 1.6;
168                color: #333;
169                white-space: pre-wrap;
170                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
171            }
172
173            .spec-list {
174                list-style: none;
175                padding: 0;
176                margin: 0;
177            }
178
179            .spec-list-item {
180                padding: 8px 0;
181                border-bottom: 1px solid #f0f0f0;
182                font-size: 14px;
183                color: #333;
184            }
185
186            .spec-list-item:last-child {
187                border-bottom: none;
188            }
189
190            .spec-error {
191                background: #fee;
192                border: 1px solid #fcc;
193                border-radius: 8px;
194                padding: 16px;
195                color: #c33;
196                font-size: 14px;
197            }
198
199            .spec-copy-button {
200                background: #667eea;
201                color: white;
202                border: none;
203                padding: 10px 20px;
204                border-radius: 6px;
205                font-size: 14px;
206                font-weight: 600;
207                cursor: pointer;
208                width: 100%;
209                margin-top: 16px;
210                transition: background 0.2s;
211            }
212
213            .spec-copy-button:hover {
214                background: #5568d3;
215            }
216
217            .spec-copy-button:active {
218                background: #4a5bc4;
219            }
220        `;
221
222        const styleElement = document.createElement('style');
223        styleElement.textContent = styles;
224        document.head.appendChild(styleElement);
225    }
226
227    // Capture screenshot of the current design
228    async function captureDesignScreenshot() {
229        try {
230            console.log('Capturing design screenshot...');
231            
232            // Find the canvas element
233            const canvas = document.querySelector('canvas');
234            if (!canvas) {
235                throw new Error('Canvas not found');
236            }
237
238            // Convert canvas to data URL
239            const dataUrl = canvas.toDataURL('image/png');
240            console.log('Screenshot captured successfully');
241            return dataUrl;
242        } catch (error) {
243            console.error('Error capturing screenshot:', error);
244            throw error;
245        }
246    }
247
248    // Extract design information from the page
249    function extractDesignInfo() {
250        const info = {
251            url: window.location.href,
252            title: document.title,
253            selectedNode: null
254        };
255
256        // Try to get selected node information from URL
257        const urlParams = new URLSearchParams(window.location.search);
258        const nodeId = urlParams.get('node-id');
259        if (nodeId) {
260            info.selectedNode = nodeId;
261        }
262
263        // Try to extract any visible text content from the design
264        const textElements = [];
265        document.querySelectorAll('[data-testid*="text"], [class*="text"]').forEach(el => {
266            const text = el.textContent?.trim();
267            if (text && text.length > 0 && text.length < 200) {
268                textElements.push(text);
269            }
270        });
271        info.visibleText = textElements.slice(0, 10); // Limit to first 10 text elements
272
273        return info;
274    }
275
276    // Generate specification using AI
277    async function generateSpecification(screenshot, designInfo) {
278        console.log('Generating specification with AI...');
279
280        const prompt = `You are a professional design specification writer. Analyze this Figma design and create a comprehensive, detailed specification document.
281
282Design Context:
283- File: ${designInfo.title}
284- URL: ${designInfo.url}
285${designInfo.selectedNode ? `- Selected Node: ${designInfo.selectedNode}` : ''}
286${designInfo.visibleText?.length > 0 ? `- Visible Text Elements: ${designInfo.visibleText.join(', ')}` : ''}
287
288Based on the design screenshot, create a detailed specification that includes:
289
2901. **Overview**: Brief description of what this design represents (screen, component, feature, etc.)
291
2922. **Layout & Structure**: Describe the overall layout, grid system, spacing, and how elements are organized
293
2943. **Components**: List all UI components visible (buttons, inputs, cards, navigation, etc.) with their properties
295
2964. **Typography**: Font families, sizes, weights, line heights, and text styles used
297
2985. **Colors**: Color palette including primary, secondary, background, text colors with hex codes if visible
299
3006. **Spacing & Sizing**: Margins, paddings, element dimensions, and spacing patterns
301
3027. **Interactive Elements**: Buttons, links, form fields and their states (hover, active, disabled, etc.)
303
3048. **Responsive Behavior**: How the design should adapt to different screen sizes (if applicable)
305
3069. **Accessibility Considerations**: Color contrast, text sizes, interactive element sizes, ARIA requirements
307
30810. **Implementation Notes**: Any special considerations for developers implementing this design
309
310Please be specific, detailed, and use professional terminology. Format the response as a structured specification document.`;
311
312        try {
313            const response = await RM.aiCall(prompt, {
314                type: "json_schema",
315                json_schema: {
316                    name: "design_specification",
317                    schema: {
318                        type: "object",
319                        properties: {
320                            overview: { type: "string" },
321                            layout: { type: "string" },
322                            components: { 
323                                type: "array", 
324                                items: { 
325                                    type: "object",
326                                    properties: {
327                                        name: { type: "string" },
328                                        description: { type: "string" },
329                                        properties: { type: "string" }
330                                    },
331                                    required: ["name", "description"]
332                                }
333                            },
334                            typography: {
335                                type: "object",
336                                properties: {
337                                    fonts: { type: "array", items: { type: "string" } },
338                                    styles: { type: "array", items: { type: "string" } }
339                                },
340                                required: ["fonts", "styles"]
341                            },
342                            colors: {
343                                type: "object",
344                                properties: {
345                                    primary: { type: "array", items: { type: "string" } },
346                                    secondary: { type: "array", items: { type: "string" } },
347                                    neutral: { type: "array", items: { type: "string" } }
348                                }
349                            },
350                            spacing: { type: "string" },
351                            interactiveElements: { type: "array", items: { type: "string" } },
352                            responsive: { type: "string" },
353                            accessibility: { type: "array", items: { type: "string" } },
354                            implementationNotes: { type: "array", items: { type: "string" } }
355                        },
356                        required: ["overview", "layout", "components"]
357                    }
358                }
359            });
360
361            console.log('Specification generated successfully');
362            return response;
363        } catch (error) {
364            console.error('Error generating specification:', error);
365            throw error;
366        }
367    }
368
369    // Format specification for display
370    function formatSpecification(spec) {
371        let html = '';
372
373        // Overview
374        if (spec.overview) {
375            html += `
376                <div class="spec-section">
377                    <h3 class="spec-section-title">📋 Overview</h3>
378                    <div class="spec-section-content">${spec.overview}</div>
379                </div>
380            `;
381        }
382
383        // Layout & Structure
384        if (spec.layout) {
385            html += `
386                <div class="spec-section">
387                    <h3 class="spec-section-title">📐 Layout & Structure</h3>
388                    <div class="spec-section-content">${spec.layout}</div>
389                </div>
390            `;
391        }
392
393        // Components
394        if (spec.components && spec.components.length > 0) {
395            html += `
396                <div class="spec-section">
397                    <h3 class="spec-section-title">🧩 Components</h3>
398                    <ul class="spec-list">
399            `;
400            spec.components.forEach(component => {
401                html += `
402                    <li class="spec-list-item">
403                        <strong>${component.name}</strong><br>
404                        ${component.description}
405                        ${component.properties ? `<br><em>${component.properties}</em>` : ''}
406                    </li>
407                `;
408            });
409            html += `</ul></div>`;
410        }
411
412        // Typography
413        if (spec.typography) {
414            html += `
415                <div class="spec-section">
416                    <h3 class="spec-section-title">✍️ Typography</h3>
417            `;
418            if (spec.typography.fonts && spec.typography.fonts.length > 0) {
419                html += `<div class="spec-section-content"><strong>Fonts:</strong><br>${spec.typography.fonts.join('<br>')}</div>`;
420            }
421            if (spec.typography.styles && spec.typography.styles.length > 0) {
422                html += `<div class="spec-section-content" style="margin-top: 12px;"><strong>Styles:</strong><br>${spec.typography.styles.join('<br>')}</div>`;
423            }
424            html += `</div>`;
425        }
426
427        // Colors
428        if (spec.colors) {
429            html += `
430                <div class="spec-section">
431                    <h3 class="spec-section-title">🎨 Colors</h3>
432            `;
433            if (spec.colors.primary && spec.colors.primary.length > 0) {
434                html += `<div class="spec-section-content"><strong>Primary:</strong><br>${spec.colors.primary.join('<br>')}</div>`;
435            }
436            if (spec.colors.secondary && spec.colors.secondary.length > 0) {
437                html += `<div class="spec-section-content" style="margin-top: 8px;"><strong>Secondary:</strong><br>${spec.colors.secondary.join('<br>')}</div>`;
438            }
439            if (spec.colors.neutral && spec.colors.neutral.length > 0) {
440                html += `<div class="spec-section-content" style="margin-top: 8px;"><strong>Neutral:</strong><br>${spec.colors.neutral.join('<br>')}</div>`;
441            }
442            html += `</div>`;
443        }
444
445        // Spacing
446        if (spec.spacing) {
447            html += `
448                <div class="spec-section">
449                    <h3 class="spec-section-title">📏 Spacing & Sizing</h3>
450                    <div class="spec-section-content">${spec.spacing}</div>
451                </div>
452            `;
453        }
454
455        // Interactive Elements
456        if (spec.interactiveElements && spec.interactiveElements.length > 0) {
457            html += `
458                <div class="spec-section">
459                    <h3 class="spec-section-title">🖱️ Interactive Elements</h3>
460                    <ul class="spec-list">
461                        ${spec.interactiveElements.map(item => `<li class="spec-list-item">${item}</li>`).join('')}
462                    </ul>
463                </div>
464            `;
465        }
466
467        // Responsive Behavior
468        if (spec.responsive) {
469            html += `
470                <div class="spec-section">
471                    <h3 class="spec-section-title">📱 Responsive Behavior</h3>
472                    <div class="spec-section-content">${spec.responsive}</div>
473                </div>
474            `;
475        }
476
477        // Accessibility
478        if (spec.accessibility && spec.accessibility.length > 0) {
479            html += `
480                <div class="spec-section">
481                    <h3 class="spec-section-title">♿ Accessibility</h3>
482                    <ul class="spec-list">
483                        ${spec.accessibility.map(item => `<li class="spec-list-item">${item}</li>`).join('')}
484                    </ul>
485                </div>
486            `;
487        }
488
489        // Implementation Notes
490        if (spec.implementationNotes && spec.implementationNotes.length > 0) {
491            html += `
492                <div class="spec-section">
493                    <h3 class="spec-section-title">💻 Implementation Notes</h3>
494                    <ul class="spec-list">
495                        ${spec.implementationNotes.map(item => `<li class="spec-list-item">${item}</li>`).join('')}
496                    </ul>
497                </div>
498            `;
499        }
500
501        return html;
502    }
503
504    // Convert specification to plain text for copying
505    function specificationToText(spec) {
506        let text = 'DESIGN SPECIFICATION\n';
507        text += '='.repeat(50) + '\n\n';
508
509        if (spec.overview) {
510            text += 'OVERVIEW\n' + '-'.repeat(50) + '\n' + spec.overview + '\n\n';
511        }
512
513        if (spec.layout) {
514            text += 'LAYOUT & STRUCTURE\n' + '-'.repeat(50) + '\n' + spec.layout + '\n\n';
515        }
516
517        if (spec.components && spec.components.length > 0) {
518            text += 'COMPONENTS\n' + '-'.repeat(50) + '\n';
519            spec.components.forEach((component, index) => {
520                text += `${index + 1}. ${component.name}\n   ${component.description}\n`;
521                if (component.properties) {
522                    text += `   Properties: ${component.properties}\n`;
523                }
524                text += '\n';
525            });
526        }
527
528        if (spec.typography) {
529            text += 'TYPOGRAPHY\n' + '-'.repeat(50) + '\n';
530            if (spec.typography.fonts) {
531                text += 'Fonts:\n' + spec.typography.fonts.map(f => '  - ' + f).join('\n') + '\n\n';
532            }
533            if (spec.typography.styles) {
534                text += 'Styles:\n' + spec.typography.styles.map(s => '  - ' + s).join('\n') + '\n\n';
535            }
536        }
537
538        if (spec.colors) {
539            text += 'COLORS\n' + '-'.repeat(50) + '\n';
540            if (spec.colors.primary) {
541                text += 'Primary:\n' + spec.colors.primary.map(c => '  - ' + c).join('\n') + '\n';
542            }
543            if (spec.colors.secondary) {
544                text += 'Secondary:\n' + spec.colors.secondary.map(c => '  - ' + c).join('\n') + '\n';
545            }
546            if (spec.colors.neutral) {
547                text += 'Neutral:\n' + spec.colors.neutral.map(c => '  - ' + c).join('\n') + '\n';
548            }
549            text += '\n';
550        }
551
552        if (spec.spacing) {
553            text += 'SPACING & SIZING\n' + '-'.repeat(50) + '\n' + spec.spacing + '\n\n';
554        }
555
556        if (spec.interactiveElements && spec.interactiveElements.length > 0) {
557            text += 'INTERACTIVE ELEMENTS\n' + '-'.repeat(50) + '\n';
558            text += spec.interactiveElements.map(item => '  - ' + item).join('\n') + '\n\n';
559        }
560
561        if (spec.responsive) {
562            text += 'RESPONSIVE BEHAVIOR\n' + '-'.repeat(50) + '\n' + spec.responsive + '\n\n';
563        }
564
565        if (spec.accessibility && spec.accessibility.length > 0) {
566            text += 'ACCESSIBILITY\n' + '-'.repeat(50) + '\n';
567            text += spec.accessibility.map(item => '  - ' + item).join('\n') + '\n\n';
568        }
569
570        if (spec.implementationNotes && spec.implementationNotes.length > 0) {
571            text += 'IMPLEMENTATION NOTES\n' + '-'.repeat(50) + '\n';
572            text += spec.implementationNotes.map(item => '  - ' + item).join('\n') + '\n\n';
573        }
574
575        return text;
576    }
577
578    // Create the specification panel
579    function createSpecPanel() {
580        const panel = document.createElement('div');
581        panel.className = 'spec-panel';
582        panel.innerHTML = `
583            <div class="spec-panel-header">
584                <h2 class="spec-panel-title">Design Specification</h2>
585                <button class="spec-panel-close" aria-label="Close panel">×</button>
586            </div>
587            <div class="spec-panel-content">
588                <div class="spec-loading">
589                    <div class="spec-spinner"></div>
590                    <p>Analyzing design and generating specification...</p>
591                </div>
592            </div>
593        `;
594
595        document.body.appendChild(panel);
596
597        // Close button handler
598        const closeButton = panel.querySelector('.spec-panel-close');
599        closeButton.addEventListener('click', () => {
600            panel.classList.remove('open');
601        });
602
603        return panel;
604    }
605
606    // Update panel with specification content
607    function updatePanelContent(panel, spec) {
608        const content = panel.querySelector('.spec-panel-content');
609        const formattedSpec = formatSpecification(spec);
610        
611        content.innerHTML = `
612            ${formattedSpec}
613            <button class="spec-copy-button">📋 Copy Specification to Clipboard</button>
614        `;
615
616        // Add copy button handler
617        const copyButton = content.querySelector('.spec-copy-button');
618        copyButton.addEventListener('click', async () => {
619            const textSpec = specificationToText(spec);
620            try {
621                await GM.setClipboard(textSpec);
622                copyButton.textContent = '✅ Copied to Clipboard!';
623                setTimeout(() => {
624                    copyButton.textContent = '📋 Copy Specification to Clipboard';
625                }, 2000);
626            } catch (error) {
627                console.error('Error copying to clipboard:', error);
628                copyButton.textContent = '❌ Copy Failed';
629                setTimeout(() => {
630                    copyButton.textContent = '📋 Copy Specification to Clipboard';
631                }, 2000);
632            }
633        });
634    }
635
636    // Show error in panel
637    function showPanelError(panel, error) {
638        const content = panel.querySelector('.spec-panel-content');
639        content.innerHTML = `
640            <div class="spec-error">
641                <strong>Error generating specification:</strong><br>
642                ${error.message || 'An unexpected error occurred. Please try again.'}
643            </div>
644        `;
645    }
646
647    // Main function to generate and display specification
648    async function handleGenerateSpec(button) {
649        try {
650            button.disabled = true;
651            button.textContent = '⏳ Generating...';
652
653            // Create and show panel
654            const panel = createSpecPanel();
655            setTimeout(() => panel.classList.add('open'), 10);
656
657            // Capture screenshot
658            const screenshot = await captureDesignScreenshot();
659
660            // Extract design info
661            const designInfo = extractDesignInfo();
662
663            // Generate specification
664            const spec = await generateSpecification(screenshot, designInfo);
665
666            // Update panel with results
667            updatePanelContent(panel, spec);
668
669            // Save to storage for later access
670            await GM.setValue('lastSpecification', JSON.stringify(spec));
671            await GM.setValue('lastSpecificationTime', Date.now());
672
673        } catch (error) {
674            console.error('Error in handleGenerateSpec:', error);
675            const panel = document.querySelector('.spec-panel');
676            if (panel) {
677                showPanelError(panel, error);
678            } else {
679                alert('Error generating specification: ' + error.message);
680            }
681        } finally {
682            button.disabled = false;
683            button.textContent = '✨ Generate Spec';
684        }
685    }
686
687    // Create the generate button
688    function createGenerateButton() {
689        const button = document.createElement('button');
690        button.className = 'spec-gen-button';
691        button.textContent = '✨ Generate Spec';
692        button.title = 'Generate design specification using AI';
693
694        button.addEventListener('click', () => handleGenerateSpec(button));
695
696        document.body.appendChild(button);
697        console.log('Generate Spec button added to page');
698    }
699
700    // Initialize the extension
701    function init() {
702        console.log('Initializing Figma Design Specification Generator...');
703        
704        // Add styles
705        addStyles();
706
707        // Wait for the page to be fully loaded
708        if (document.readyState === 'loading') {
709            document.addEventListener('DOMContentLoaded', createGenerateButton);
710        } else {
711            createGenerateButton();
712        }
713
714        // Re-add button if it gets removed (e.g., during navigation)
715        const observer = new MutationObserver(debounce(() => {
716            if (!document.querySelector('.spec-gen-button')) {
717                console.log('Button removed, re-adding...');
718                createGenerateButton();
719            }
720        }, 1000));
721
722        observer.observe(document.body, {
723            childList: true,
724            subtree: true
725        });
726    }
727
728    // Start the extension
729    init();
730
731})();
Figma Design Specification Generator | Robomonkey