|
15 | 15 | }; |
16 | 16 |
|
17 | 17 | let mounted = $state(false); |
| 18 | + let copyMessage = $state(''); |
| 19 | + let copyStatus = $state<'success' | 'error'>('success'); |
| 20 | + let copyMessageTimeout: ReturnType<typeof setTimeout> | undefined; |
| 21 | +
|
18 | 22 | onMount(() => { |
19 | 23 | mounted = true; |
| 24 | +
|
| 25 | + return () => { |
| 26 | + if (copyMessageTimeout) { |
| 27 | + clearTimeout(copyMessageTimeout); |
| 28 | + } |
| 29 | + }; |
20 | 30 | }); |
| 31 | +
|
| 32 | + function showCopyMessage(message: string, status: 'success' | 'error') { |
| 33 | + copyMessage = message; |
| 34 | + copyStatus = status; |
| 35 | +
|
| 36 | + if (copyMessageTimeout) { |
| 37 | + clearTimeout(copyMessageTimeout); |
| 38 | + } |
| 39 | +
|
| 40 | + copyMessageTimeout = setTimeout(() => { |
| 41 | + copyMessage = ''; |
| 42 | + }, 3000); |
| 43 | + } |
| 44 | +
|
| 45 | + async function copyProfileUrl() { |
| 46 | + if (!navigator.clipboard?.writeText) { |
| 47 | + showCopyMessage('Clipboard API unavailable. Copy the URL from your address bar.', 'error'); |
| 48 | + return; |
| 49 | + } |
| 50 | +
|
| 51 | + try { |
| 52 | + await navigator.clipboard.writeText(window.location.href); |
| 53 | + showCopyMessage('Profile link copied.', 'success'); |
| 54 | + } catch { |
| 55 | + showCopyMessage('Could not copy link. Copy the URL from your address bar.', 'error'); |
| 56 | + } |
| 57 | + } |
21 | 58 | </script> |
22 | 59 |
|
23 | 60 | <svelte:head> |
|
96 | 133 |
|
97 | 134 | <div class="get-your-own"> |
98 | 135 | <p>Want a card like this?</p> |
99 | | - <a href="/" class="gradient-text">Create your DevCard ⚡</a> |
| 136 | + <div class="profile-actions"> |
| 137 | + <a href="/" class="gradient-text get-devcard-link">Create your DevCard ⚡</a> |
| 138 | + <button type="button" class="copy-link-button" onclick={copyProfileUrl}> |
| 139 | + Copy Link |
| 140 | + </button> |
| 141 | + </div> |
| 142 | + <p class="copy-message {copyStatus}" aria-live="polite"> |
| 143 | + {copyMessage} |
| 144 | + </p> |
100 | 145 | </div> |
101 | 146 | {/if} |
102 | 147 | </main> |
|
301 | 346 | margin-bottom: 0.5rem; |
302 | 347 | } |
303 | 348 |
|
304 | | - .get-your-own a { |
| 349 | + .profile-actions { |
| 350 | + display: flex; |
| 351 | + flex-wrap: wrap; |
| 352 | + align-items: center; |
| 353 | + justify-content: center; |
| 354 | + gap: 0.75rem; |
| 355 | + } |
| 356 | +
|
| 357 | + .get-devcard-link { |
305 | 358 | font-weight: 700; |
306 | 359 | font-size: 1.1rem; |
307 | 360 | } |
308 | 361 |
|
| 362 | + .copy-link-button { |
| 363 | + border: 1px solid var(--border-glass); |
| 364 | + border-radius: var(--radius); |
| 365 | + background: rgba(255, 255, 255, 0.08); |
| 366 | + color: var(--text-primary); |
| 367 | + cursor: pointer; |
| 368 | + font: inherit; |
| 369 | + font-weight: 700; |
| 370 | + padding: 0.65rem 1rem; |
| 371 | + transition: all 0.2s ease; |
| 372 | + } |
| 373 | +
|
| 374 | + .copy-link-button:hover { |
| 375 | + background: rgba(255, 255, 255, 0.15); |
| 376 | + transform: translateY(-1px); |
| 377 | + } |
| 378 | +
|
| 379 | + .copy-link-button:focus-visible { |
| 380 | + outline: 2px solid var(--accent); |
| 381 | + outline-offset: 3px; |
| 382 | + } |
| 383 | +
|
| 384 | + .copy-message { |
| 385 | + min-height: 1.2rem; |
| 386 | + margin-top: 0.75rem; |
| 387 | + margin-bottom: 0; |
| 388 | + font-size: 0.85rem; |
| 389 | + } |
| 390 | +
|
| 391 | + .copy-message.success { |
| 392 | + color: var(--text-secondary); |
| 393 | + } |
| 394 | +
|
| 395 | + .copy-message.error { |
| 396 | + color: #ef4444; |
| 397 | + } |
| 398 | +
|
309 | 399 | .error-glass { |
310 | 400 | text-align: center; |
311 | 401 | padding: 4rem; |
|
0 commit comments