[READ-ONLY] Mirror of https://github.com/mrgnw/morganwill.com. morganwill.com
0

Configure Feed

Select the types of activity you want to include in your feed.

constistent qr / qrs styling

+28 -97
+26 -89
src/components/LinkIcons.svelte
··· 2 2 import { onMount } from "svelte"; 3 3 import { fade } from "svelte/transition"; 4 4 import { innerWidth, innerHeight } from "svelte/reactivity/window"; 5 + import ColoredQr from "./ColoredQr.svelte"; 5 6 6 7 // Icon imports - managed internally 7 8 import JamLinkedinCircle from "~icons/jam/linkedin-circle"; ··· 250 251 251 252 // Reactive size unit for grid 252 253 let sizeUnit = $derived(size ? `${size}px` : `${initialSize}vh`); 253 - 254 - /** 255 - * Build SVG gradient defs from colors array 256 - * @param {string[]} colors 257 - * @param {string} id 258 - * @param {number} size - SVG viewBox size 259 - * @returns {string} 260 - */ 261 - function buildGradientDefs(colors, id, size) { 262 - const stops = colors.map((color, i) => { 263 - const offset = (i / (colors.length - 1)) * 100; 264 - return `<stop offset="${offset}%" stop-color="${color}"/>`; 265 - }).join(''); 266 - // Use userSpaceOnUse so gradient spans the entire SVG, not each rect 267 - // Diagonal from top-left to bottom-right 268 - return `<defs><linearGradient id="${id}" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="${size}" y2="${size}">${stops}</linearGradient></defs>`; 269 - } 270 - 271 - /** 272 - * Svelte action to use shadow DOM for gradient isolation 273 - * @param {HTMLElement} node 274 - * @param {{ qr: string, colors: string[], id: string }} params 275 - */ 276 - function shadowGradient(node, { qr, colors, id }) { 277 - const shadowRoot = node.attachShadow({ mode: 'open' }); 278 - 279 - // Extract the viewBox size from the SVG 280 - const sizeMatch = qr.match(/viewBox="0 0 (\d+) (\d+)"/); 281 - const svgSize = sizeMatch ? parseInt(sizeMatch[1]) : 164; 282 - 283 - // Inject the gradient defs into the SVG 284 - const gradientDefs = buildGradientDefs(colors, id, svgSize); 285 - // Replace fill="currentColor" with fill="url(#id)" 286 - const coloredQr = qr.replace(/fill="currentColor"/g, `fill="url(#${id})"`); 287 - // Insert defs after opening svg tag 288 - const svgWithDefs = coloredQr.replace(/(<svg[^>]*>)/, `$1${gradientDefs}`); 289 - 290 - shadowRoot.innerHTML = ` 291 - <style> 292 - :host { display: contents; } 293 - svg { width: 100%; height: 100%; } 294 - rect { 295 - opacity: 0; 296 - animation: fadeIn 0.15s ease-out forwards; 297 - } 298 - @keyframes fadeIn { 299 - to { opacity: 1; } 300 - } 301 - </style> 302 - ${svgWithDefs} 303 - `; 304 - 305 - return { 306 - destroy() { 307 - // Cleanup if needed 308 - } 309 - }; 310 - } 311 254 </script> 312 255 313 256 <div class="link-icons" class:qrs-mode={qrsMode}> ··· 321 264 > 322 265 {#each links as link, index (link.title)} 323 266 {@const animOrder = cardOrder.get(index) ?? index} 324 - {@const isGradient = (link.colors?.length ?? 0) > 1} 325 - {@const singleColor = link.colors?.[0] ?? '#888'} 326 267 <a 327 268 href={link.url} 328 269 target="_blank" ··· 331 272 onmouseenter={() => hoveredCard = index} 332 273 onmouseleave={() => hoveredCard = null} 333 274 > 334 - {#if isGradient && link.qr && link.colors} 335 - <div 336 - class="qr-card-code" 337 - use:shadowGradient={{ qr: link.qr, colors: link.colors, id: `grad-${link.title}` }} 338 - use:waveAction={100 + animOrder * 80} 339 - ></div> 340 - {:else} 341 - <div 342 - class="qr-card-code" 343 - style="--brand-color: {singleColor}" 344 - use:waveAction={100 + animOrder * 80} 345 - > 346 - {@html link.qr} 347 - </div> 348 - {/if} 275 + <div 276 + class="qr-card-code" 277 + use:waveAction={100 + animOrder * 80} 278 + > 279 + {#if link.qr} 280 + <ColoredQr 281 + qr={link.qr} 282 + colors={link.colors ?? ['#888']} 283 + id="qr-{link.title}" 284 + animate={false} 285 + /> 286 + {/if} 287 + </div> 349 288 <span class="qr-card-title">{link.title}</span> 350 289 </a> 351 290 {/each} 352 291 </div> 353 292 {:else} 354 293 <h1 class="title" ondblclick={toggleQrMode}> 355 - {#if qrMode && selectedQr} 294 + {#if qrMode && selectedLink?.qr} 356 295 <div class="qr-wrapper"> 357 296 {#if selectedUrl} 358 297 <div class="url-display" transition:fade={{ duration: 300 }}> ··· 365 304 </a> 366 305 </div> 367 306 {/if} 368 - {#if typeof selectedQr === "string"} 369 - {@html selectedQr} 370 - {/if} 307 + <ColoredQr 308 + qr={selectedLink.qr} 309 + colors={selectedLink.colors ?? ['#888']} 310 + id="qr-{selectedLink.title}" 311 + animate={false} 312 + /> 371 313 </div> 372 314 {:else} 373 315 {selected ?? defaultTitle} ··· 512 454 height: 100%; 513 455 min-width: 0; 514 456 min-height: 0; 515 - color: var(--brand-color, #888); 516 457 position: relative; 517 458 } 518 459 ··· 549 490 550 491 /* Animate individual QR modules (rects) with radial bloom effect */ 551 492 .qr-card-code :global(svg rect) { 552 - fill: currentColor; 553 493 opacity: 0; 554 494 transform-origin: center center; 555 495 transform: scale(0) rotate(180deg); ··· 610 550 fill: transparent; 611 551 } 612 552 613 - .qr-card:hover .qr-card-code { 614 - color: var(--highlight); 615 - } 616 - 617 553 .qr-card:hover .qr-card-code :global(svg path:last-child) { 618 554 stroke: var(--highlight); 619 - } 620 - 621 - .qr-card:hover .qr-card-code :global(svg rect) { 622 - fill: currentColor; 623 555 } 624 556 625 557 .qr-card:hover .qr-card-title { ··· 645 577 display: flex; 646 578 flex-direction: column; 647 579 align-items: center; 580 + } 581 + 582 + .qr-wrapper :global(.colored-qr) { 583 + width: 164px; 584 + height: 164px; 648 585 } 649 586 650 587 .qr-wrapper :global(svg) {
+2 -8
src/routes/+page.server.js
··· 176 176 const linksWithQr = await Promise.all( 177 177 combinedLinks.map(async (link) => { 178 178 const qrUrl = link.shortUrl ?? link.url; 179 - // Use animated SVG (individual rects) for qrs mode 180 - const qr = willBeQrsMode 181 - ? generateAnimatedQRSvg(qrUrl, 164) 182 - : await QRCode.toString(qrUrl, { 183 - type: "svg", 184 - width: 164, 185 - margin: 0, 186 - }); 179 + // Always use our custom SVG format with fill="currentColor" for consistent styling 180 + const qr = generateAnimatedQRSvg(qrUrl, 164); 187 181 return { ...link, qr }; 188 182 }) 189 183 );