your personal website on atproto - mirror blento.app

v0.2

jycouet bbcdbb12 bc24bc8f

+71 -65
+1 -2
src/lib/cards/BlueskyProfileCard/BlueskyProfileCard.svelte
··· 14 14 use:qrOverlay={{ 15 15 disabled: isEditing, 16 16 context: { 17 - title: item.cardData.displayName || item.cardData.handle, 18 - avatar: item.cardData.avatar 17 + title: item.cardData.displayName || item.cardData.handle 19 18 } 20 19 }} 21 20 >
+6
src/lib/cards/EventCard/EventCard.svelte
··· 7 7 import type { EventData } from '.'; 8 8 import { parseUri } from '$lib/atproto'; 9 9 import { browser } from '$app/environment'; 10 + import { qrOverlay } from '$lib/components/qr/qrOverlay.svelte'; 10 11 11 12 let { item }: ContentComponentProps = $props(); 12 13 ··· 268 269 class="absolute inset-0 h-full w-full" 269 270 target="_blank" 270 271 rel="noopener noreferrer" 272 + use:qrOverlay={{ 273 + context: { 274 + title: eventData?.name ?? '' 275 + } 276 + }} 271 277 > 272 278 <span class="sr-only">View event on smokesignal.events</span> 273 279 </a>
+52 -7
src/lib/components/qr/QRCodeDisplay.svelte
··· 1 1 <script lang="ts"> 2 2 import { onMount } from 'svelte'; 3 3 4 - let { url, size = 280 }: { url: string; size?: number } = $props(); 4 + let { 5 + url, 6 + icon, 7 + iconColor, 8 + class: className = '' 9 + }: { 10 + url: string; 11 + icon?: string; 12 + iconColor?: string; 13 + class?: string; 14 + } = $props(); 5 15 6 16 let container: HTMLDivElement | undefined = $state(); 7 17 18 + // Convert SVG string to data URI for use as QR center image 19 + function svgToDataUri(svg: string, color: string): string { 20 + // Add fill color to SVG - insert fill attribute on the svg tag 21 + let coloredSvg = svg; 22 + if (!svg.includes('fill=')) { 23 + // No fill attribute, add it to the svg tag 24 + coloredSvg = svg.replace('<svg', `<svg fill="${color}"`); 25 + } else { 26 + // Replace existing fill attributes 27 + coloredSvg = svg.replace(/fill="[^"]*"/g, `fill="${color}"`); 28 + } 29 + const encoded = encodeURIComponent(coloredSvg); 30 + return `data:image/svg+xml,${encoded}`; 31 + } 32 + 8 33 onMount(async () => { 9 34 if (!container) return; 10 35 36 + // Use iconColor or default accent, ensure # prefix 37 + const rawColor = iconColor || 'f6339a'; 38 + const dotColor = rawColor.startsWith('#') ? rawColor : `#${rawColor}`; 39 + 11 40 const module = await import('qr-code-styling'); 12 41 const QRCodeStyling = module.default; 13 42 43 + // Get container size for responsive QR 44 + const rect = container.getBoundingClientRect(); 45 + const size = Math.min(rect.width, rect.height) || 280; 46 + 14 47 const options: ConstructorParameters<typeof QRCodeStyling>[0] = { 15 48 width: size, 16 49 height: size, 17 50 data: url, 18 51 dotsOptions: { 19 - color: '#000', 52 + color: dotColor, 20 53 type: 'rounded' 21 54 }, 22 55 backgroundOptions: { 23 - color: '#fff' 56 + color: '#FFF' 24 57 }, 25 58 cornersSquareOptions: { 26 - type: 'extra-rounded' 59 + type: 'extra-rounded', 60 + color: dotColor 27 61 }, 28 62 cornersDotOptions: { 29 - type: 'dot' 30 - } 63 + type: 'dot', 64 + color: dotColor 65 + }, 66 + margin: 10 31 67 }; 32 68 69 + // Add icon as center image if provided (as SVG string) 70 + if (icon) { 71 + options.image = svgToDataUri(icon, dotColor); 72 + options.imageOptions = { 73 + margin: 10, 74 + imageSize: 0.5 75 + }; 76 + } 77 + 33 78 const qrCode = new QRCodeStyling(options); 34 79 qrCode.append(container); 35 80 }); 36 81 </script> 37 82 38 - <div bind:this={container} class="flex items-center justify-center"></div> 83 + <div bind:this={container} class="flex items-center justify-center {className}"></div>
+11 -54
src/lib/components/qr/QRCodeModal.svelte
··· 1 1 <script lang="ts"> 2 - import { Modal, Button, toast } from '@foxui/core'; 2 + import { Modal } from '@foxui/core'; 3 3 import QRCodeDisplay from './QRCodeDisplay.svelte'; 4 4 5 5 export type QRContext = { 6 6 title?: string; 7 7 icon?: string; 8 8 iconColor?: string; 9 - favicon?: string; 10 - avatar?: string; 11 9 }; 12 10 13 11 let { ··· 19 17 href: string; 20 18 context?: QRContext; 21 19 } = $props(); 22 - 23 - async function copyUrl() { 24 - try { 25 - await navigator.clipboard.writeText(href); 26 - toast.success('URL copied!'); 27 - } catch { 28 - toast.error('Failed to copy'); 29 - } 30 - } 31 20 </script> 32 21 33 - <Modal bind:open closeButton={true} class="max-w-sm"> 34 - <div class="flex flex-col items-center gap-4 p-2"> 35 - {#if context.icon} 36 - <div 37 - class="flex size-14 items-center justify-center rounded-2xl [&_svg]:size-8 [&_svg]:fill-white" 38 - style:background-color={context.iconColor ? `#${context.iconColor}` : '#000'} 39 - > 40 - {@html context.icon} 41 - </div> 42 - {:else if context.avatar} 43 - <img src={context.avatar} alt="" class="size-14 rounded-full object-cover" /> 44 - {:else if context.favicon} 45 - <img src={context.favicon} alt="" class="size-10 rounded-lg object-cover" /> 46 - {/if} 47 - 22 + <Modal bind:open closeButton={true} class="max-w-[90vw]! sm:max-w-sm! md:max-w-md!"> 23 + <div class="flex flex-col items-center justify-center gap-4 p-4"> 48 24 {#if context.title} 49 - <div class="text-base-900 dark:text-base-100 text-lg font-semibold"> 25 + <div class="text-base-900 dark:text-base-100 text-center text-xl font-semibold"> 50 26 {context.title} 51 27 </div> 52 28 {/if} 53 29 54 - <div class="overflow-hidden rounded-2xl"> 55 - <QRCodeDisplay url={href} size={280} /> 56 - </div> 57 - 58 - <div class="flex w-full items-center gap-2"> 59 - <div 60 - class="bg-base-100 dark:bg-base-800 text-base-600 dark:text-base-400 flex-1 truncate rounded-lg px-3 py-2 text-sm" 61 - > 62 - {href} 63 - </div> 64 - <Button onclick={copyUrl} variant="ghost" size="sm"> 65 - <svg 66 - xmlns="http://www.w3.org/2000/svg" 67 - fill="none" 68 - viewBox="0 0 24 24" 69 - stroke-width="1.5" 70 - stroke="currentColor" 71 - class="size-4" 72 - > 73 - <path 74 - stroke-linecap="round" 75 - stroke-linejoin="round" 76 - d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9.75a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" 77 - /> 78 - </svg> 79 - </Button> 30 + <div class="flex items-center justify-center overflow-hidden rounded-2xl"> 31 + <QRCodeDisplay 32 + url={href} 33 + icon={context.icon} 34 + iconColor={context.iconColor} 35 + class="size-[min(70vw,320px)] sm:size-72 md:size-80" 36 + /> 80 37 </div> 81 38 </div> 82 39 </Modal>
+1 -2
src/lib/website/Profile.svelte
··· 35 35 class="w-fit" 36 36 use:qrOverlay={{ 37 37 context: { 38 - title: getName(data), 39 - avatar: data.profile.avatar 38 + title: getName(data) + "'s blento" 40 39 } 41 40 }} 42 41 >