File size: 8,234 Bytes
213c234
 
 
 
 
 
 
 
 
 
 
 
6eff815
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deb38a3
6eff815
f444918
 
213c234
d60484d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213c234
f444918
c6f0353
deb38a3
 
 
c6f0353
 
213c234
deb38a3
f444918
c681f46
213c234
 
c6f0353
213c234
 
deb38a3
 
 
 
 
 
 
 
 
 
213c234
deb38a3
213c234
deb38a3
 
 
 
 
 
 
 
 
 
213c234
deb38a3
 
 
f444918
 
 
 
 
 
213c234
6eff815
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c681f46
 
 
 
6eff815
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c681f46
 
6eff815
 
 
 
 
c681f46
 
6eff815
 
 
 
 
 
 
 
c681f46
 
6eff815
 
 
 
 
 
 
 
 
 
 
2a88efb
213c234
2a88efb
c6f0353
2a88efb
213c234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import type { PicletInstance } from '$lib/db/schema';
import { embedPicletMetadata } from './picletMetadata';

/**
 * Generates a shareable image of a piclet with embedded metadata
 */
export async function generateShareableImage(piclet: PicletInstance): Promise<Blob> {
  // Create canvas
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  if (!ctx) throw new Error('Could not create canvas context');
  
  // Polyfill for roundRect if not available
  if (!ctx.roundRect) {
    ctx.roundRect = function(x: number, y: number, width: number, height: number, radius: number) {
      ctx.beginPath();
      ctx.moveTo(x + radius, y);
      ctx.lineTo(x + width - radius, y);
      ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
      ctx.lineTo(x + width, y + height - radius);
      ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
      ctx.lineTo(x + radius, y + height);
      ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
      ctx.lineTo(x, y + radius);
      ctx.quadraticCurveTo(x, y, x + radius, y);
      ctx.closePath();
    };
  }
  
  // Set canvas size - full piclet size plus padding for name and stats
  const canvasWidth = 1024;
  const canvasHeight = 1400; // Extra height for name and stats
  canvas.width = canvasWidth;
  canvas.height = canvasHeight;
  
  // Fill with striped background like battle view
  ctx.fillStyle = '#f8f9fa';
  ctx.fillRect(0, 0, canvasWidth, canvasHeight);
  
  // Create striped pattern
  const stripeHeight = 10;
  ctx.fillStyle = 'rgba(76, 175, 80, 0.2)'; // Light green
  for (let y = 0; y < canvasHeight; y += stripeHeight * 2) {
    ctx.fillRect(0, y, canvasWidth, stripeHeight);
  }
  
  // Add alternating darker stripes
  ctx.fillStyle = 'rgba(76, 175, 80, 0.1)'; // Lighter green
  for (let y = stripeHeight; y < canvasHeight; y += stripeHeight * 2) {
    ctx.fillRect(0, y, canvasWidth, stripeHeight);
  }
  
  // Load piclet image first
  const picletImg = await loadImage(piclet.imageData || piclet.imageUrl);
  const picletSize = 1024; // Full size!
  const picletX = 0;
  const picletY = 150; // Leave room for name at top
  
  // Load and draw grass platform positioned under the piclet
  const grassImg = await loadImage('/assets/grass.PNG');
  const platformSize = 1200; // Larger than piclet for proper positioning
  const platformX = (canvasWidth - platformSize) / 2;
  const platformY = picletY + picletSize - 300 - 384; // Platform moved up by 384px
  ctx.drawImage(grassImg, platformX, platformY, platformSize, platformSize);
  
  // Draw piclet on top of platform
  ctx.drawImage(picletImg, picletX, picletY, picletSize, picletSize);
  
  // Add sleek modern text styling
  const nameText = piclet.nickname || piclet.typeId;
  
  // Create gradient for text
  const gradient = ctx.createLinearGradient(0, 50, 0, 120);
  gradient.addColorStop(0, '#ffffff');
  gradient.addColorStop(1, '#e0e0e0');
  
  // Modern sleek font with gradient and subtle shadow
  ctx.font = 'bold 72px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  
  // Subtle shadow for depth
  ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
  ctx.shadowBlur = 15;
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 3;
  
  // Dark outline for contrast
  ctx.strokeStyle = '#2c3e50';
  ctx.lineWidth = 4;
  ctx.strokeText(nameText.toUpperCase(), canvasWidth / 2, 80);
  
  // Fill with gradient
  ctx.fillStyle = gradient;
  ctx.fillText(nameText.toUpperCase(), canvasWidth / 2, 80);
  
  // Reset shadow
  ctx.shadowColor = 'transparent';
  ctx.shadowBlur = 0;
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 0;
  
  // Draw base stats in a sleek format
  const statsY = picletY + picletSize + 50; // Position below piclet
  const stats = [
    { label: 'HP', value: piclet.baseHp, color: '#4caf50' },
    { label: 'ATK', value: piclet.baseAttack, color: '#f44336' },
    { label: 'DEF', value: piclet.baseDefense, color: '#2196f3' },
    { label: 'S.ATK', value: piclet.baseFieldAttack, color: '#ff9800' },
    { label: 'S.DEF', value: piclet.baseFieldDefense, color: '#9c27b0' },
    { label: 'SPD', value: piclet.baseSpeed, color: '#00bcd4' }
  ];
  
  // Stats container background
  const containerX = 100;
  const containerY = statsY - 20;
  const containerWidth = canvasWidth - 200;
  const containerHeight = 150;
  
  // Draw semi-transparent background using the polyfilled roundRect
  ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
  ctx.beginPath();
  (ctx as any).roundRect(containerX, containerY, containerWidth, containerHeight, 20);
  ctx.fill();
  
  // Draw stats bars
  const barHeight = 16;
  const barSpacing = 20;
  const maxStatValue = 255;
  const barMaxWidth = containerWidth - 240; // Leave room for labels and values
  
  stats.forEach((stat, index) => {
    const y = statsY + (index * barSpacing);
    
    // Draw stat label
    ctx.font = 'bold 14px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif';
    ctx.fillStyle = '#ffffff';
    ctx.textAlign = 'left';
    ctx.fillText(stat.label, containerX + 20, y + 3);
    
    // Draw stat value
    ctx.textAlign = 'right';
    ctx.fillText(stat.value.toString(), containerX + 100, y + 3);
    
    // Draw background bar
    const barX = containerX + 120;
    ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
    ctx.beginPath();
    (ctx as any).roundRect(barX, y - 8, barMaxWidth, barHeight, 8);
    ctx.fill();
    
    // Draw filled bar
    const fillWidth = (stat.value / maxStatValue) * barMaxWidth;
    ctx.fillStyle = stat.color;
    ctx.beginPath();
    (ctx as any).roundRect(barX, y - 8, fillWidth, barHeight, 8);
    ctx.fill();
    
    // Add shine effect
    const shineGradient = ctx.createLinearGradient(barX, y - 8, barX, y - 8 + barHeight);
    shineGradient.addColorStop(0, 'rgba(255, 255, 255, 0.3)');
    shineGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
    shineGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
    ctx.fillStyle = shineGradient;
    ctx.beginPath();
    (ctx as any).roundRect(barX, y - 8, fillWidth, barHeight / 2, 8);
    ctx.fill();
  });
  
  // Draw BST (Base Stat Total)
  const bst = piclet.bst || (piclet.baseHp + piclet.baseAttack + piclet.baseDefense + 
                             piclet.baseFieldAttack + piclet.baseFieldDefense + piclet.baseSpeed);
  ctx.font = 'bold 18px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif';
  ctx.fillStyle = '#ffd700'; // Gold color
  ctx.textAlign = 'center';
  ctx.fillText(`BST: ${bst}`, canvasWidth / 2, containerY + containerHeight - 10);
  
  // Load and draw translucent watermark in top-right
  const logoImg = await loadImage('/assets/snap_logo.png');
  const logoSize = 120; // Slightly smaller for top placement
  ctx.globalAlpha = 0.3; // More translucent
  ctx.drawImage(logoImg, canvasWidth - logoSize - 30, 30, logoSize, logoSize);
  ctx.globalAlpha = 1.0;
  
  // Get the image as blob
  const blob = await canvasToBlob(canvas);
  
  // Embed metadata in the blob
  return embedPicletMetadata(blob, piclet);
}

/**
 * Downloads a piclet card image
 */
export async function downloadPicletCard(piclet: PicletInstance, filename?: string): Promise<void> {
  const blob = await generateShareableImage(piclet);
  const url = URL.createObjectURL(blob);
  
  const a = document.createElement('a');
  a.href = url;
  a.download = filename || `Piclet_${piclet.nickname || piclet.typeId}_Lv${piclet.level}.png`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  
  URL.revokeObjectURL(url);
}

/**
 * Helper to load an image
 */
function loadImage(src: string): Promise<HTMLImageElement> {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = src;
  });
}

/**
 * Convert canvas to blob
 */
function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {
  return new Promise((resolve, reject) => {
    canvas.toBlob((blob) => {
      if (blob) resolve(blob);
      else reject(new Error('Failed to create blob'));
    }, 'image/png');
  });
}