img
Browse files
src/lib/components/Piclets/PicletDetail.svelte
CHANGED
@@ -83,6 +83,19 @@
|
|
83 |
<div class="content-scroll">
|
84 |
<!-- Header Card -->
|
85 |
<div class="header-card">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
<div class="header-layout">
|
87 |
<div class="image-column">
|
88 |
<div class="image-container">
|
@@ -242,12 +255,7 @@
|
|
242 |
<button class="btn btn-danger" onclick={handleDelete}>Yes, Release</button>
|
243 |
<button class="btn btn-secondary" onclick={() => showDeleteConfirm = false}>Cancel</button>
|
244 |
{:else}
|
245 |
-
<
|
246 |
-
<button class="btn btn-primary" onclick={handleShare} disabled={isSharing}>
|
247 |
-
{isSharing ? 'Creating...' : 'Share Piclet'}
|
248 |
-
</button>
|
249 |
-
<button class="btn btn-danger" onclick={() => showDeleteConfirm = true}>Release Piclet</button>
|
250 |
-
</div>
|
251 |
{/if}
|
252 |
</div>
|
253 |
</div>
|
@@ -315,6 +323,43 @@
|
|
315 |
border-radius: 16px;
|
316 |
background: linear-gradient(135deg, #636366, #48484a);
|
317 |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
318 |
}
|
319 |
|
320 |
.header-layout {
|
|
|
83 |
<div class="content-scroll">
|
84 |
<!-- Header Card -->
|
85 |
<div class="header-card">
|
86 |
+
<button
|
87 |
+
class="share-button"
|
88 |
+
onclick={handleShare}
|
89 |
+
disabled={isSharing}
|
90 |
+
aria-label="Share Piclet"
|
91 |
+
>
|
92 |
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
93 |
+
<path d="M4 12v8a2 2 0 002 2h12a2 2 0 002-2v-8"></path>
|
94 |
+
<polyline points="16 6 12 2 8 6"></polyline>
|
95 |
+
<line x1="12" y1="2" x2="12" y2="15"></line>
|
96 |
+
</svg>
|
97 |
+
</button>
|
98 |
+
|
99 |
<div class="header-layout">
|
100 |
<div class="image-column">
|
101 |
<div class="image-container">
|
|
|
255 |
<button class="btn btn-danger" onclick={handleDelete}>Yes, Release</button>
|
256 |
<button class="btn btn-secondary" onclick={() => showDeleteConfirm = false}>Cancel</button>
|
257 |
{:else}
|
258 |
+
<button class="btn btn-danger" onclick={() => showDeleteConfirm = true}>Release Piclet</button>
|
|
|
|
|
|
|
|
|
|
|
259 |
{/if}
|
260 |
</div>
|
261 |
</div>
|
|
|
323 |
border-radius: 16px;
|
324 |
background: linear-gradient(135deg, #636366, #48484a);
|
325 |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
326 |
+
position: relative;
|
327 |
+
}
|
328 |
+
|
329 |
+
.share-button {
|
330 |
+
position: absolute;
|
331 |
+
top: 16px;
|
332 |
+
right: 16px;
|
333 |
+
width: 40px;
|
334 |
+
height: 40px;
|
335 |
+
border-radius: 20px;
|
336 |
+
background: rgba(255, 255, 255, 0.2);
|
337 |
+
border: none;
|
338 |
+
cursor: pointer;
|
339 |
+
display: flex;
|
340 |
+
align-items: center;
|
341 |
+
justify-content: center;
|
342 |
+
transition: all 0.2s;
|
343 |
+
color: white;
|
344 |
+
}
|
345 |
+
|
346 |
+
.share-button:hover {
|
347 |
+
background: rgba(255, 255, 255, 0.3);
|
348 |
+
transform: scale(1.05);
|
349 |
+
}
|
350 |
+
|
351 |
+
.share-button:active {
|
352 |
+
transform: scale(0.95);
|
353 |
+
}
|
354 |
+
|
355 |
+
.share-button:disabled {
|
356 |
+
opacity: 0.5;
|
357 |
+
cursor: not-allowed;
|
358 |
+
}
|
359 |
+
|
360 |
+
.share-button svg {
|
361 |
+
width: 20px;
|
362 |
+
height: 20px;
|
363 |
}
|
364 |
|
365 |
.header-layout {
|
src/lib/services/picletExport.ts
CHANGED
@@ -10,49 +10,61 @@ export async function generateShareableImage(piclet: PicletInstance): Promise<Bl
|
|
10 |
const ctx = canvas.getContext('2d');
|
11 |
if (!ctx) throw new Error('Could not create canvas context');
|
12 |
|
13 |
-
// Set canvas size to match
|
14 |
-
const
|
15 |
-
|
16 |
-
canvas.
|
|
|
17 |
|
18 |
// Fill background with solid sky blue
|
19 |
ctx.fillStyle = '#87CEEB';
|
20 |
-
ctx.fillRect(0, 0,
|
21 |
|
22 |
-
// Load piclet image first
|
23 |
const picletImg = await loadImage(piclet.imageData || piclet.imageUrl);
|
24 |
-
const picletSize = 512;
|
25 |
-
const picletX = (
|
26 |
-
const picletY =
|
27 |
|
28 |
// Load and draw grass platform positioned under the piclet
|
29 |
const grassImg = await loadImage('/assets/grass.PNG');
|
30 |
const platformSize = picletSize + 100; // Slightly larger than piclet
|
31 |
-
const platformX = (
|
32 |
-
const platformY = picletY + picletSize -
|
33 |
ctx.drawImage(grassImg, platformX, platformY, platformSize, platformSize);
|
34 |
|
35 |
// Draw piclet on top of platform
|
36 |
ctx.drawImage(picletImg, picletX, picletY, picletSize, picletSize);
|
37 |
|
38 |
-
// Add piclet name
|
39 |
ctx.fillStyle = 'white';
|
40 |
-
ctx.strokeStyle = '
|
41 |
-
ctx.lineWidth =
|
42 |
-
|
|
|
43 |
ctx.textAlign = 'center';
|
|
|
|
|
|
|
|
|
44 |
|
45 |
const nameText = piclet.nickname || piclet.typeId;
|
46 |
|
47 |
-
// Draw name with outline
|
48 |
-
ctx.strokeText(nameText,
|
49 |
-
ctx.fillText(nameText,
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
|
51 |
// Load and draw translucent watermark
|
52 |
const logoImg = await loadImage('/assets/snap_logo.png');
|
53 |
-
const logoSize =
|
54 |
ctx.globalAlpha = 0.3; // More translucent
|
55 |
-
ctx.drawImage(logoImg,
|
56 |
ctx.globalAlpha = 1.0;
|
57 |
|
58 |
// Get the image as blob
|
|
|
10 |
const ctx = canvas.getContext('2d');
|
11 |
if (!ctx) throw new Error('Could not create canvas context');
|
12 |
|
13 |
+
// Set canvas size - narrower width to match content
|
14 |
+
const canvasWidth = 700;
|
15 |
+
const canvasHeight = 1536; // Taller to accommodate piclet at bottom
|
16 |
+
canvas.width = canvasWidth;
|
17 |
+
canvas.height = canvasHeight;
|
18 |
|
19 |
// Fill background with solid sky blue
|
20 |
ctx.fillStyle = '#87CEEB';
|
21 |
+
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
|
22 |
|
23 |
+
// Load piclet image first
|
24 |
const picletImg = await loadImage(piclet.imageData || piclet.imageUrl);
|
25 |
+
const picletSize = 512;
|
26 |
+
const picletX = (canvasWidth - picletSize) / 2;
|
27 |
+
const picletY = canvasHeight - picletSize - 50; // Position near bottom with small margin
|
28 |
|
29 |
// Load and draw grass platform positioned under the piclet
|
30 |
const grassImg = await loadImage('/assets/grass.PNG');
|
31 |
const platformSize = picletSize + 100; // Slightly larger than piclet
|
32 |
+
const platformX = (canvasWidth - platformSize) / 2;
|
33 |
+
const platformY = picletY + picletSize - 180; // Position so piclet sits on platform
|
34 |
ctx.drawImage(grassImg, platformX, platformY, platformSize, platformSize);
|
35 |
|
36 |
// Draw piclet on top of platform
|
37 |
ctx.drawImage(picletImg, picletX, picletY, picletSize, picletSize);
|
38 |
|
39 |
+
// Add piclet name with video game font
|
40 |
ctx.fillStyle = 'white';
|
41 |
+
ctx.strokeStyle = '#1e3a8a'; // Dark blue outline
|
42 |
+
ctx.lineWidth = 8;
|
43 |
+
// Try to use a more gaming-style font, fallback to Impact
|
44 |
+
ctx.font = 'bold 72px "Press Start 2P", "Courier New", Impact, monospace';
|
45 |
ctx.textAlign = 'center';
|
46 |
+
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
|
47 |
+
ctx.shadowBlur = 10;
|
48 |
+
ctx.shadowOffsetX = 4;
|
49 |
+
ctx.shadowOffsetY = 4;
|
50 |
|
51 |
const nameText = piclet.nickname || piclet.typeId;
|
52 |
|
53 |
+
// Draw name with outline and shadow
|
54 |
+
ctx.strokeText(nameText, canvasWidth / 2, 150);
|
55 |
+
ctx.fillText(nameText, canvasWidth / 2, 150);
|
56 |
+
|
57 |
+
// Reset shadow
|
58 |
+
ctx.shadowColor = 'transparent';
|
59 |
+
ctx.shadowBlur = 0;
|
60 |
+
ctx.shadowOffsetX = 0;
|
61 |
+
ctx.shadowOffsetY = 0;
|
62 |
|
63 |
// Load and draw translucent watermark
|
64 |
const logoImg = await loadImage('/assets/snap_logo.png');
|
65 |
+
const logoSize = 120;
|
66 |
ctx.globalAlpha = 0.3; // More translucent
|
67 |
+
ctx.drawImage(logoImg, canvasWidth - logoSize - 20, canvasHeight - logoSize - 20, logoSize, logoSize);
|
68 |
ctx.globalAlpha = 1.0;
|
69 |
|
70 |
// Get the image as blob
|