your personal website on atproto - mirror blento.app

fluid text

+45 -9
+45 -9
src/lib/cards/visual/FluidTextCard/FluidTextCard.svelte
··· 1 1 <script lang="ts"> 2 - import { colorToHue, getCSSVar, getHexOfCardColor } from '../../helper'; 2 + import { colorToHue, getHexCSSVar, getHexOfCardColor } from '../../helper'; 3 3 import type { ContentComponentProps } from '../../types'; 4 4 import { onMount, onDestroy, tick } from 'svelte'; 5 5 let { item }: ContentComponentProps = $props(); ··· 13 13 let maskReady = false; 14 14 let isInitialized = $state(false); 15 15 let resizeObserver: ResizeObserver | null = null; 16 + let themeObserver: MutationObserver | null = null; 16 17 17 18 // Pure hash function for shader keyword caching 18 19 function hashCode(s: string) { ··· 132 133 ctx.clearRect(0, 0, maskCanvas.width, maskCanvas.height); 133 134 ctx.scale(dpr, dpr); 134 135 135 - //const color = getCSSVar('--color-base-900'); 136 - 137 - ctx.fillStyle = 'black'; 136 + const isDark = document.documentElement.classList.contains('dark'); 137 + const bgColor = 138 + item.color === 'transparent' 139 + ? getHexCSSVar(isDark ? '--color-base-900' : '--color-base-50') 140 + : 'black'; 141 + ctx.fillStyle = bgColor; 138 142 ctx.fillRect(0, 0, width, height); 139 143 140 144 // Font size as percentage of container width 141 145 const textFontSize = Math.round(width * fontSize); 142 146 ctx.font = `${fontWeight} ${textFontSize}px ${fontFamily}`; 143 147 144 - ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; 145 - ctx.lineWidth = 2; 148 + ctx.lineWidth = 3; 146 149 ctx.textAlign = 'center'; 147 150 148 151 const metrics = ctx.measureText(text); ··· 157 160 ctx.textBaseline = 'middle'; 158 161 } 159 162 160 - ctx.strokeText(text, width / 2, textY); 163 + if (item.color === 'transparent') { 164 + // Partially cut out the stroke area so fluid shows through 165 + ctx.globalCompositeOperation = 'destination-out'; 166 + ctx.globalAlpha = 0.7; 167 + ctx.strokeStyle = 'white'; 168 + ctx.strokeText(text, width / 2, textY); 169 + ctx.globalAlpha = 1; 170 + ctx.globalCompositeOperation = 'source-over'; 171 + 172 + // Add overlay: brighten in dark mode, darken in light mode 173 + ctx.strokeStyle = isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.2)'; 174 + ctx.strokeText(text, width / 2, textY); 175 + } else { 176 + ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; 177 + ctx.strokeText(text, width / 2, textY); 178 + } 179 + 161 180 ctx.globalCompositeOperation = 'destination-out'; 162 181 ctx.fillText(text, width / 2, textY); 163 182 ctx.globalCompositeOperation = 'source-over'; ··· 214 233 if (isInitialized) scheduleMaskDraw(); 215 234 }); 216 235 } 236 + 237 + // Watch for dark mode changes to redraw mask with correct background 238 + if (item.color === 'transparent') { 239 + themeObserver = new MutationObserver(() => { 240 + if (isInitialized) scheduleMaskDraw(); 241 + }); 242 + themeObserver.observe(document.documentElement, { 243 + attributes: true, 244 + attributeFilter: ['class'] 245 + }); 246 + } 217 247 }); 218 248 219 249 onDestroy(() => { ··· 221 251 if (splatIntervalId) clearInterval(splatIntervalId); 222 252 if (maskDrawRaf) cancelAnimationFrame(maskDrawRaf); 223 253 if (resizeObserver) resizeObserver.disconnect(); 254 + if (themeObserver) themeObserver.disconnect(); 224 255 }); 225 256 226 257 function initFluidSimulation(startHue: number, endHue: number) { ··· 246 277 COLOR_UPDATE_SPEED: 10, 247 278 PAUSED: false, 248 279 BACK_COLOR: { r: 0, g: 0, b: 0 }, 249 - TRANSPARENT: false, 280 + TRANSPARENT: item.color === 'transparent', 250 281 BLOOM: false, 251 282 BLOOM_ITERATIONS: 8, 252 283 BLOOM_RESOLUTION: 256, ··· 1701 1732 } 1702 1733 </script> 1703 1734 1704 - <div bind:this={container} class="relative h-full w-full overflow-hidden bg-black"> 1735 + <div 1736 + bind:this={container} 1737 + class="relative h-full w-full overflow-hidden {item.color === 'transparent' 1738 + ? 'bg-base-50 dark:bg-base-900' 1739 + : 'bg-black'}" 1740 + > 1705 1741 <canvas bind:this={fluidCanvas} class="absolute h-full w-full"></canvas> 1706 1742 <canvas bind:this={maskCanvas} class="absolute h-full w-full"></canvas> 1707 1743 </div>