mirror of
https://github.com/f/awesome-chatgpt-prompts.git
synced 2025-06-22 15:35:04 +00:00
update
This commit is contained in:
parent
d9a42a6bc7
commit
a8a69caf76
@ -31,7 +31,7 @@
|
||||
<!-- Viewer Mode -->
|
||||
<div id="viewer-mode" class="viewer-mode h-screen flex flex-col p-2 sm:p-4">
|
||||
<!-- Context Pills -->
|
||||
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden mb-0 sm:mb-2 pb-1 scrollbar-hide min-h-[25px]"></div>
|
||||
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden mb-0 sm:mb-2 pb-1 scrollbar-hide"></div>
|
||||
|
||||
<!-- Main Prompt Interface - Full Height -->
|
||||
<div class="flex-1 flex flex-col">
|
||||
@ -46,13 +46,6 @@
|
||||
<!-- Settings Pills -->
|
||||
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div>
|
||||
|
||||
<!-- Download Button -->
|
||||
<button id="download-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-muted text-dynamic-foreground rounded-full flex items-center justify-center hover:bg-dynamic-muted/80 transition-colors focus-ring flex-shrink-0 shadow-lg touch-target mr-2" title="Download as PNG">
|
||||
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Send Button (circular with arrow up) -->
|
||||
<button id="copy-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-primary text-white rounded-full flex items-center justify-center hover:opacity-90 transition-opacity focus-ring flex-shrink-0 shadow-lg touch-target" title="Send prompt">
|
||||
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||
|
@ -177,11 +177,6 @@ class EmbedPreview {
|
||||
if (copyButton) {
|
||||
copyButton.addEventListener('click', () => this.handleCopy());
|
||||
}
|
||||
|
||||
const downloadButton = document.getElementById('download-button');
|
||||
if (downloadButton) {
|
||||
downloadButton.addEventListener('click', () => this.handleDownload());
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -376,253 +371,6 @@ class EmbedPreview {
|
||||
}
|
||||
}
|
||||
|
||||
async handleDownload() {
|
||||
try {
|
||||
// Get computed styles
|
||||
const computedStyles = getComputedStyle(document.documentElement);
|
||||
const bgColor = `rgb(${computedStyles.getPropertyValue('--background').trim()})`;
|
||||
const fgColor = `rgb(${computedStyles.getPropertyValue('--foreground').trim()})`;
|
||||
const mutedColor = `rgb(${computedStyles.getPropertyValue('--muted').trim()})`;
|
||||
const mutedFgColor = `rgb(${computedStyles.getPropertyValue('--muted-foreground').trim()})`;
|
||||
const primaryColor = `rgb(${computedStyles.getPropertyValue('--primary').trim()})`;
|
||||
const borderColor = `rgb(${computedStyles.getPropertyValue('--border').trim()})`;
|
||||
|
||||
// Create SVG representation
|
||||
const { svg, scale } = this.createSVG({
|
||||
bgColor,
|
||||
fgColor,
|
||||
mutedColor,
|
||||
mutedFgColor,
|
||||
primaryColor,
|
||||
borderColor
|
||||
});
|
||||
|
||||
// Convert SVG to PNG and download (scale will make it retina-compatible)
|
||||
await this.downloadSVGasPNG(svg, 'prompt-preview.png', scale);
|
||||
this.showNotification('Downloaded as PNG!');
|
||||
} catch (error) {
|
||||
console.error('Download failed:', error);
|
||||
this.showNotification('Download failed');
|
||||
}
|
||||
}
|
||||
|
||||
createSVG(colors) {
|
||||
const scale = 2; // 2x for retina displays
|
||||
const width = 800 * scale;
|
||||
const padding = 16 * scale; // Match sm:p-4 (1rem = 16px)
|
||||
const mobileGap = 4 * scale; // Match gap-1 on mobile
|
||||
const desktopGap = 8 * scale; // Match sm:gap-2
|
||||
|
||||
// Start calculating height dynamically
|
||||
let yOffset = padding;
|
||||
let svgContent = '';
|
||||
|
||||
// Context pills area with min-height of 25px
|
||||
const contextPills = document.getElementById('context-pills');
|
||||
const contextPillsHeight = 25 * scale; // min-h-[25px]
|
||||
const contextPillsMargin = 8 * scale; // mb-0 sm:mb-2 (using desktop margin)
|
||||
|
||||
if (contextPills && contextPills.children.length > 0) {
|
||||
let xOffset = padding;
|
||||
const pillHeight = 20 * scale; // Smaller pills in context area
|
||||
const pillPadding = 8 * scale;
|
||||
const fontSize = 10.4 * scale;
|
||||
|
||||
for (const pill of contextPills.children) {
|
||||
const pillText = pill.textContent.trim();
|
||||
const pillWidth = Math.max(60 * scale, pillText.length * 7 * scale + pillPadding * 2);
|
||||
|
||||
// Pill background with proper opacity
|
||||
const pillY = yOffset + (contextPillsHeight - pillHeight) / 2;
|
||||
svgContent += `<rect x="${xOffset}" y="${pillY}" width="${pillWidth}" height="${pillHeight}" rx="${10 * scale}" fill="${colors.primaryColor}" fill-opacity="0.1" stroke="${colors.primaryColor}" stroke-opacity="0.3" stroke-width="${scale}"/>`;
|
||||
|
||||
// Pill text - fix vertical alignment using dominant-baseline
|
||||
const textY = pillY + pillHeight / 2;
|
||||
svgContent += `<text x="${xOffset + pillWidth/2}" y="${textY}" text-anchor="middle" dominant-baseline="middle" fill="${colors.fgColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${fontSize}" font-weight="500">${this.escapeXML(pillText)}</text>`;
|
||||
|
||||
xOffset += pillWidth + desktopGap;
|
||||
}
|
||||
}
|
||||
|
||||
yOffset += contextPillsHeight + contextPillsMargin;
|
||||
|
||||
// Main prompt container - calculate needed height
|
||||
const promptText = this.config.prompt || '← Enter your prompt on designer...';
|
||||
const isPlaceholder = !this.config.prompt;
|
||||
const containerPadding = 24 * scale; // p-3 sm:p-6 (using desktop padding)
|
||||
const lineHeight = 22 * scale; // leading-relaxed
|
||||
const fontSize = 16 * scale; // text-sm sm:text-base (using desktop size)
|
||||
|
||||
// Calculate wrapped text lines
|
||||
const maxTextWidth = width - padding * 2 - containerPadding * 2;
|
||||
const lines = this.wrapText(promptText, maxTextWidth / scale, 16); // Use unscaled values for text wrapping
|
||||
const textHeight = Math.max(100 * scale, lines.length * lineHeight + containerPadding * 2);
|
||||
|
||||
// Prompt container background
|
||||
svgContent += `<rect x="${padding}" y="${yOffset}" width="${width - padding * 2}" height="${textHeight}" rx="${12 * scale}" fill="${colors.mutedColor}" stroke="${colors.borderColor}" stroke-width="${scale}"/>`;
|
||||
|
||||
// Prompt text
|
||||
const textColor = isPlaceholder ? colors.mutedFgColor : colors.fgColor;
|
||||
const fontStyle = isPlaceholder ? 'italic' : 'normal';
|
||||
let textY = yOffset + containerPadding + fontSize;
|
||||
|
||||
for (const line of lines) {
|
||||
svgContent += `<text x="${padding + containerPadding}" y="${textY}" fill="${textColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${fontSize}" font-style="${fontStyle}">${this.escapeXML(line)}</text>`;
|
||||
textY += lineHeight;
|
||||
}
|
||||
|
||||
yOffset += textHeight + desktopGap; // mt-0 sm:mt-2
|
||||
|
||||
// Bottom bar with settings pills and buttons
|
||||
const bottomBarHeight = 40 * scale; // h-10 for buttons
|
||||
const settingsY = yOffset + (bottomBarHeight - 28 * scale) / 2; // Center pills vertically
|
||||
let settingsX = padding;
|
||||
|
||||
// Mode pill (with background)
|
||||
if (this.config.mode) {
|
||||
const modeText = this.capitalizeFirst(this.config.mode);
|
||||
const modePadding = 12 * scale; // px-3
|
||||
const modeHeight = 32 * scale; // py-2
|
||||
const modeWidth = modeText.length * 8 * scale + modePadding * 2;
|
||||
const modeFontSize = 12 * scale;
|
||||
|
||||
svgContent += `<rect x="${settingsX}" y="${settingsY}" width="${modeWidth}" height="${modeHeight}" rx="${16 * scale}" fill="${colors.primaryColor}" fill-opacity="0.1" stroke="${colors.primaryColor}" stroke-opacity="0.2" stroke-width="${scale}"/>`;
|
||||
svgContent += `<text x="${settingsX + modeWidth/2}" y="${settingsY + modeHeight/2}" text-anchor="middle" dominant-baseline="middle" fill="${colors.fgColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${modeFontSize}" font-weight="500">${modeText}</text>`;
|
||||
|
||||
settingsX += modeWidth + desktopGap;
|
||||
}
|
||||
|
||||
// Model pill (text only)
|
||||
if (this.config.model) {
|
||||
const modelText = this.config.model;
|
||||
const modelFontSize = 12 * scale;
|
||||
svgContent += `<text x="${settingsX + 4 * scale}" y="${yOffset + bottomBarHeight/2}" dominant-baseline="middle" fill="${colors.primaryColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${modelFontSize}">${modelText}</text>`;
|
||||
settingsX += modelText.length * 7 * scale + 12 * scale;
|
||||
}
|
||||
|
||||
// Thinking pill
|
||||
if (this.config.thinking) {
|
||||
const thinkingFontSize = 12 * scale;
|
||||
svgContent += `<text x="${settingsX}" y="${yOffset + bottomBarHeight/2}" dominant-baseline="middle" fill="${colors.primaryColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${thinkingFontSize}">🧠 Thinking</text>`;
|
||||
settingsX += 70 * scale;
|
||||
}
|
||||
|
||||
// MAX pill
|
||||
if (this.config.max) {
|
||||
const maxFontSize = 12 * scale;
|
||||
svgContent += `<text x="${settingsX}" y="${yOffset + bottomBarHeight/2}" dominant-baseline="middle" fill="${colors.primaryColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${maxFontSize}">⚡ MAX</text>`;
|
||||
}
|
||||
|
||||
// Watermark on the right
|
||||
const watermarkText = "prompts.chat/embed";
|
||||
const watermarkFontSize = 14 * scale;
|
||||
const watermarkX = width - padding - watermarkText.length * 7 * scale;
|
||||
const watermarkY = yOffset + bottomBarHeight / 2;
|
||||
|
||||
// Add watermark text with subtle styling
|
||||
svgContent += `<text x="${watermarkX}" y="${watermarkY}" dominant-baseline="middle" fill="${colors.mutedFgColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${watermarkFontSize}" font-weight="400" opacity="0.7">${watermarkText}</text>`;
|
||||
|
||||
// Calculate final height
|
||||
const totalHeight = yOffset + bottomBarHeight + padding;
|
||||
|
||||
// Create SVG with calculated height
|
||||
let svg = `<svg width="${width}" height="${totalHeight}" viewBox="0 0 ${width} ${totalHeight}" xmlns="http://www.w3.org/2000/svg">`;
|
||||
|
||||
// Background
|
||||
svg += `<rect width="${width}" height="${totalHeight}" fill="${colors.bgColor}"/>`;
|
||||
|
||||
// Add all content
|
||||
svg += svgContent;
|
||||
|
||||
svg += '</svg>';
|
||||
|
||||
return { svg, scale };
|
||||
}
|
||||
|
||||
wrapText(text, maxWidth, fontSize) {
|
||||
const words = text.split(' ');
|
||||
const lines = [];
|
||||
let currentLine = '';
|
||||
// More accurate character width calculation based on font size
|
||||
const avgCharWidth = fontSize * 0.55; // Adjusted for typical character width
|
||||
|
||||
for (const word of words) {
|
||||
const testLine = currentLine ? currentLine + ' ' + word : word;
|
||||
const testWidth = testLine.length * avgCharWidth;
|
||||
|
||||
if (testWidth > maxWidth && currentLine) {
|
||||
lines.push(currentLine);
|
||||
currentLine = word;
|
||||
} else {
|
||||
currentLine = testLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLine) {
|
||||
lines.push(currentLine);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
escapeXML(text) {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
async downloadSVGasPNG(svgString, filename, scale = 1) {
|
||||
// Create a blob from the SVG string
|
||||
const blob = new Blob([svgString], { type: 'image/svg+xml' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Create an image element
|
||||
const img = new Image();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
img.onload = () => {
|
||||
// Create canvas with the actual pixel dimensions
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Enable high quality rendering for retina
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = 'high';
|
||||
|
||||
// Draw the image at full resolution
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
// Convert to PNG and download
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(a.href);
|
||||
}
|
||||
URL.revokeObjectURL(url);
|
||||
resolve();
|
||||
}, 'image/png', 1.0); // Maximum quality
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
reject(new Error('Failed to load SVG'));
|
||||
};
|
||||
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
showNotification(message) {
|
||||
const notification = document.getElementById('notification');
|
||||
if (!notification) return;
|
||||
|
@ -31,7 +31,7 @@
|
||||
<!-- Viewer Mode -->
|
||||
<div id="viewer-mode" class="viewer-mode h-screen flex flex-col p-2 sm:p-4">
|
||||
<!-- Context Pills -->
|
||||
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden mb-0 sm:mb-2 pb-1 scrollbar-hide min-h-[25px]"></div>
|
||||
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden mb-0 sm:mb-2 pb-1 scrollbar-hide"></div>
|
||||
|
||||
<!-- Main Prompt Interface - Full Height -->
|
||||
<div class="flex-1 flex flex-col">
|
||||
@ -46,13 +46,6 @@
|
||||
<!-- Settings Pills -->
|
||||
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div>
|
||||
|
||||
<!-- Download Button -->
|
||||
<button id="download-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-muted text-dynamic-foreground rounded-full flex items-center justify-center hover:bg-dynamic-muted/80 transition-colors focus-ring flex-shrink-0 shadow-lg touch-target mr-2" title="Download as PNG">
|
||||
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Send Button (circular with arrow up) -->
|
||||
<button id="copy-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-primary text-white rounded-full flex items-center justify-center hover:opacity-90 transition-opacity focus-ring flex-shrink-0 shadow-lg touch-target" title="Send prompt">
|
||||
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||
|
@ -177,11 +177,6 @@ class EmbedPreview {
|
||||
if (copyButton) {
|
||||
copyButton.addEventListener('click', () => this.handleCopy());
|
||||
}
|
||||
|
||||
const downloadButton = document.getElementById('download-button');
|
||||
if (downloadButton) {
|
||||
downloadButton.addEventListener('click', () => this.handleDownload());
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -376,253 +371,6 @@ class EmbedPreview {
|
||||
}
|
||||
}
|
||||
|
||||
async handleDownload() {
|
||||
try {
|
||||
// Get computed styles
|
||||
const computedStyles = getComputedStyle(document.documentElement);
|
||||
const bgColor = `rgb(${computedStyles.getPropertyValue('--background').trim()})`;
|
||||
const fgColor = `rgb(${computedStyles.getPropertyValue('--foreground').trim()})`;
|
||||
const mutedColor = `rgb(${computedStyles.getPropertyValue('--muted').trim()})`;
|
||||
const mutedFgColor = `rgb(${computedStyles.getPropertyValue('--muted-foreground').trim()})`;
|
||||
const primaryColor = `rgb(${computedStyles.getPropertyValue('--primary').trim()})`;
|
||||
const borderColor = `rgb(${computedStyles.getPropertyValue('--border').trim()})`;
|
||||
|
||||
// Create SVG representation
|
||||
const { svg, scale } = this.createSVG({
|
||||
bgColor,
|
||||
fgColor,
|
||||
mutedColor,
|
||||
mutedFgColor,
|
||||
primaryColor,
|
||||
borderColor
|
||||
});
|
||||
|
||||
// Convert SVG to PNG and download (scale will make it retina-compatible)
|
||||
await this.downloadSVGasPNG(svg, 'prompt-preview.png', scale);
|
||||
this.showNotification('Downloaded as PNG!');
|
||||
} catch (error) {
|
||||
console.error('Download failed:', error);
|
||||
this.showNotification('Download failed');
|
||||
}
|
||||
}
|
||||
|
||||
createSVG(colors) {
|
||||
const scale = 2; // 2x for retina displays
|
||||
const width = 800 * scale;
|
||||
const padding = 16 * scale; // Match sm:p-4 (1rem = 16px)
|
||||
const mobileGap = 4 * scale; // Match gap-1 on mobile
|
||||
const desktopGap = 8 * scale; // Match sm:gap-2
|
||||
|
||||
// Start calculating height dynamically
|
||||
let yOffset = padding;
|
||||
let svgContent = '';
|
||||
|
||||
// Context pills area with min-height of 25px
|
||||
const contextPills = document.getElementById('context-pills');
|
||||
const contextPillsHeight = 25 * scale; // min-h-[25px]
|
||||
const contextPillsMargin = 8 * scale; // mb-0 sm:mb-2 (using desktop margin)
|
||||
|
||||
if (contextPills && contextPills.children.length > 0) {
|
||||
let xOffset = padding;
|
||||
const pillHeight = 20 * scale; // Smaller pills in context area
|
||||
const pillPadding = 8 * scale;
|
||||
const fontSize = 10.4 * scale;
|
||||
|
||||
for (const pill of contextPills.children) {
|
||||
const pillText = pill.textContent.trim();
|
||||
const pillWidth = Math.max(60 * scale, pillText.length * 7 * scale + pillPadding * 2);
|
||||
|
||||
// Pill background with proper opacity
|
||||
const pillY = yOffset + (contextPillsHeight - pillHeight) / 2;
|
||||
svgContent += `<rect x="${xOffset}" y="${pillY}" width="${pillWidth}" height="${pillHeight}" rx="${10 * scale}" fill="${colors.primaryColor}" fill-opacity="0.1" stroke="${colors.primaryColor}" stroke-opacity="0.3" stroke-width="${scale}"/>`;
|
||||
|
||||
// Pill text - fix vertical alignment using dominant-baseline
|
||||
const textY = pillY + pillHeight / 2;
|
||||
svgContent += `<text x="${xOffset + pillWidth/2}" y="${textY}" text-anchor="middle" dominant-baseline="middle" fill="${colors.fgColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${fontSize}" font-weight="500">${this.escapeXML(pillText)}</text>`;
|
||||
|
||||
xOffset += pillWidth + desktopGap;
|
||||
}
|
||||
}
|
||||
|
||||
yOffset += contextPillsHeight + contextPillsMargin;
|
||||
|
||||
// Main prompt container - calculate needed height
|
||||
const promptText = this.config.prompt || '← Enter your prompt on designer...';
|
||||
const isPlaceholder = !this.config.prompt;
|
||||
const containerPadding = 24 * scale; // p-3 sm:p-6 (using desktop padding)
|
||||
const lineHeight = 22 * scale; // leading-relaxed
|
||||
const fontSize = 16 * scale; // text-sm sm:text-base (using desktop size)
|
||||
|
||||
// Calculate wrapped text lines
|
||||
const maxTextWidth = width - padding * 2 - containerPadding * 2;
|
||||
const lines = this.wrapText(promptText, maxTextWidth / scale, 16); // Use unscaled values for text wrapping
|
||||
const textHeight = Math.max(100 * scale, lines.length * lineHeight + containerPadding * 2);
|
||||
|
||||
// Prompt container background
|
||||
svgContent += `<rect x="${padding}" y="${yOffset}" width="${width - padding * 2}" height="${textHeight}" rx="${12 * scale}" fill="${colors.mutedColor}" stroke="${colors.borderColor}" stroke-width="${scale}"/>`;
|
||||
|
||||
// Prompt text
|
||||
const textColor = isPlaceholder ? colors.mutedFgColor : colors.fgColor;
|
||||
const fontStyle = isPlaceholder ? 'italic' : 'normal';
|
||||
let textY = yOffset + containerPadding + fontSize;
|
||||
|
||||
for (const line of lines) {
|
||||
svgContent += `<text x="${padding + containerPadding}" y="${textY}" fill="${textColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${fontSize}" font-style="${fontStyle}">${this.escapeXML(line)}</text>`;
|
||||
textY += lineHeight;
|
||||
}
|
||||
|
||||
yOffset += textHeight + desktopGap; // mt-0 sm:mt-2
|
||||
|
||||
// Bottom bar with settings pills and buttons
|
||||
const bottomBarHeight = 40 * scale; // h-10 for buttons
|
||||
const settingsY = yOffset + (bottomBarHeight - 28 * scale) / 2; // Center pills vertically
|
||||
let settingsX = padding;
|
||||
|
||||
// Mode pill (with background)
|
||||
if (this.config.mode) {
|
||||
const modeText = this.capitalizeFirst(this.config.mode);
|
||||
const modePadding = 12 * scale; // px-3
|
||||
const modeHeight = 32 * scale; // py-2
|
||||
const modeWidth = modeText.length * 8 * scale + modePadding * 2;
|
||||
const modeFontSize = 12 * scale;
|
||||
|
||||
svgContent += `<rect x="${settingsX}" y="${settingsY}" width="${modeWidth}" height="${modeHeight}" rx="${16 * scale}" fill="${colors.primaryColor}" fill-opacity="0.1" stroke="${colors.primaryColor}" stroke-opacity="0.2" stroke-width="${scale}"/>`;
|
||||
svgContent += `<text x="${settingsX + modeWidth/2}" y="${settingsY + modeHeight/2}" text-anchor="middle" dominant-baseline="middle" fill="${colors.fgColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${modeFontSize}" font-weight="500">${modeText}</text>`;
|
||||
|
||||
settingsX += modeWidth + desktopGap;
|
||||
}
|
||||
|
||||
// Model pill (text only)
|
||||
if (this.config.model) {
|
||||
const modelText = this.config.model;
|
||||
const modelFontSize = 12 * scale;
|
||||
svgContent += `<text x="${settingsX + 4 * scale}" y="${yOffset + bottomBarHeight/2}" dominant-baseline="middle" fill="${colors.primaryColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${modelFontSize}">${modelText}</text>`;
|
||||
settingsX += modelText.length * 7 * scale + 12 * scale;
|
||||
}
|
||||
|
||||
// Thinking pill
|
||||
if (this.config.thinking) {
|
||||
const thinkingFontSize = 12 * scale;
|
||||
svgContent += `<text x="${settingsX}" y="${yOffset + bottomBarHeight/2}" dominant-baseline="middle" fill="${colors.primaryColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${thinkingFontSize}">🧠 Thinking</text>`;
|
||||
settingsX += 70 * scale;
|
||||
}
|
||||
|
||||
// MAX pill
|
||||
if (this.config.max) {
|
||||
const maxFontSize = 12 * scale;
|
||||
svgContent += `<text x="${settingsX}" y="${yOffset + bottomBarHeight/2}" dominant-baseline="middle" fill="${colors.primaryColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${maxFontSize}">⚡ MAX</text>`;
|
||||
}
|
||||
|
||||
// Watermark on the right
|
||||
const watermarkText = "prompts.chat/embed";
|
||||
const watermarkFontSize = 14 * scale;
|
||||
const watermarkX = width - padding - watermarkText.length * 7 * scale;
|
||||
const watermarkY = yOffset + bottomBarHeight / 2;
|
||||
|
||||
// Add watermark text with subtle styling
|
||||
svgContent += `<text x="${watermarkX}" y="${watermarkY}" dominant-baseline="middle" fill="${colors.mutedFgColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${watermarkFontSize}" font-weight="400" opacity="0.7">${watermarkText}</text>`;
|
||||
|
||||
// Calculate final height
|
||||
const totalHeight = yOffset + bottomBarHeight + padding;
|
||||
|
||||
// Create SVG with calculated height
|
||||
let svg = `<svg width="${width}" height="${totalHeight}" viewBox="0 0 ${width} ${totalHeight}" xmlns="http://www.w3.org/2000/svg">`;
|
||||
|
||||
// Background
|
||||
svg += `<rect width="${width}" height="${totalHeight}" fill="${colors.bgColor}"/>`;
|
||||
|
||||
// Add all content
|
||||
svg += svgContent;
|
||||
|
||||
svg += '</svg>';
|
||||
|
||||
return { svg, scale };
|
||||
}
|
||||
|
||||
wrapText(text, maxWidth, fontSize) {
|
||||
const words = text.split(' ');
|
||||
const lines = [];
|
||||
let currentLine = '';
|
||||
// More accurate character width calculation based on font size
|
||||
const avgCharWidth = fontSize * 0.55; // Adjusted for typical character width
|
||||
|
||||
for (const word of words) {
|
||||
const testLine = currentLine ? currentLine + ' ' + word : word;
|
||||
const testWidth = testLine.length * avgCharWidth;
|
||||
|
||||
if (testWidth > maxWidth && currentLine) {
|
||||
lines.push(currentLine);
|
||||
currentLine = word;
|
||||
} else {
|
||||
currentLine = testLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLine) {
|
||||
lines.push(currentLine);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
escapeXML(text) {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
async downloadSVGasPNG(svgString, filename, scale = 1) {
|
||||
// Create a blob from the SVG string
|
||||
const blob = new Blob([svgString], { type: 'image/svg+xml' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Create an image element
|
||||
const img = new Image();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
img.onload = () => {
|
||||
// Create canvas with the actual pixel dimensions
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Enable high quality rendering for retina
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = 'high';
|
||||
|
||||
// Draw the image at full resolution
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
// Convert to PNG and download
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(a.href);
|
||||
}
|
||||
URL.revokeObjectURL(url);
|
||||
resolve();
|
||||
}, 'image/png', 1.0); // Maximum quality
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
reject(new Error('Failed to load SVG'));
|
||||
};
|
||||
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
showNotification(message) {
|
||||
const notification = document.getElementById('notification');
|
||||
if (!notification) return;
|
||||
|
Loading…
x
Reference in New Issue
Block a user