your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import { getStroke } from 'perfect-freehand';
3 import type { ContentComponentProps } from '../types';
4
5 let { item = $bindable(), isEditing }: ContentComponentProps = $props();
6
7 type Stroke = {
8 points: [number, number, number][];
9 size?: number;
10 };
11
12 function getStrokeOptions(size: number) {
13 return { size, thinning: 0.5, smoothing: 0.5, streamline: 0.5 };
14 }
15
16 function getSvgPathFromStroke(stroke: number[][]): string {
17 if (!stroke.length) return '';
18
19 const d = stroke.reduce(
20 (acc, [x0, y0], i, arr) => {
21 const [x1, y1] = arr[(i + 1) % arr.length];
22 acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2);
23 return acc;
24 },
25 ['M', ...stroke[0], 'Q'] as (string | number)[]
26 );
27
28 d.push('Z');
29 return d.join(' ');
30 }
31
32 // Parse strokes from JSON string stored in cardData
33 function parseStrokes(): Stroke[] {
34 const strokesJson = item.cardData.strokesJson as string | undefined;
35 if (!strokesJson) return [];
36 try {
37 return JSON.parse(strokesJson) as Stroke[];
38 } catch {
39 return [];
40 }
41 }
42
43 let strokes = $derived(parseStrokes());
44 let viewBox = $derived((item.cardData.viewBox as string) || '0 0 100 100');
45</script>
46
47<svg class="absolute inset-0 h-full w-full" {viewBox} preserveAspectRatio="xMidYMid meet">
48 {#each strokes as stroke, index (index)}
49 {@const pathData = getSvgPathFromStroke(
50 getStroke(stroke.points, getStrokeOptions(stroke.size ?? 3))
51 )}
52 <path d={pathData} class="accent:fill-white fill-black dark:fill-white" />
53 {/each}
54</svg>