tangled
alpha
login
or
join now
flo-bit.dev
/
blento
your personal website on atproto - mirror
blento.app
20
fork
atom
overview
issues
pulls
pipelines
small fixes
Florian
4 weeks ago
613d2ebd
bae16167
+99
-48
6 changed files
expand all
collapse all
unified
split
src
lib
EditableWebsite.svelte
cards
BaseCard
BaseCard.svelte
BaseEditingCard.svelte
BlueskyMediaCard
Video.svelte
utils
MarkdownTextEditor.svelte
routes
[handle]
og.png
+server.ts
+2
-21
src/lib/EditableWebsite.svelte
···
299
}
300
301
// Auto-scroll when dragging near top or bottom of viewport
302
-
const scrollZone = 150;
303
-
const scrollSpeed = 15;
304
const viewportHeight = window.innerHeight;
305
306
if (e.clientY < scrollZone) {
···
373
</BaseEditingCard>
374
<!-- {/if} -->
375
{/each}
376
-
377
-
{#if activeDragElement.element && activeDragElement.x >= 0 && activeDragElement.item}
378
-
{@const finalPos = simulateFinalPosition(
379
-
items,
380
-
activeDragElement.item,
381
-
activeDragElement.x,
382
-
activeDragElement.y,
383
-
isMobile
384
-
)}
385
-
<div
386
-
class={[
387
-
'bg-base-500/10 absolute aspect-square rounded-2xl transition-transform duration-100'
388
-
]}
389
-
style={`translate: calc(${(finalPos.x / COLUMNS) * 100}cqw + ${margin}px) calc(${(finalPos.y / COLUMNS) * 100}cqw + ${margin}px);
390
-
391
-
width: calc(${(getW(activeDragElement.item) / COLUMNS) * 100}cqw - ${margin * 2}px);
392
-
height: calc(${(getH(activeDragElement.item) / COLUMNS) * 100}cqw - ${margin * 2}px);`}
393
-
></div>
394
-
{/if}
395
396
<div style="height: {((maxHeight + 2) / 8) * 100}cqw;"></div>
397
</div>
···
299
}
300
301
// Auto-scroll when dragging near top or bottom of viewport
302
+
const scrollZone = 100;
303
+
const scrollSpeed = 10;
304
const viewportHeight = window.innerHeight;
305
306
if (e.clientY < scrollZone) {
···
373
</BaseEditingCard>
374
<!-- {/if} -->
375
{/each}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
376
377
<div style="height: {((maxHeight + 2) / 8) * 100}cqw;"></div>
378
</div>
+4
-4
src/lib/cards/BaseCard/BaseCard.svelte
···
7
import { getColor } from '..';
8
9
const colors = {
10
-
base: 'border-base-300 shadow-lg dark:shadow-none inset-shadow-sm inset-shadow-base-500/10 shadow-base-900/5 bg-base-50 dark:border-base-700 dark:bg-base-900 border',
11
accent:
12
-
'border-accent-300 shadow-lg inset-shadow-sm inset-shadow-accent-500/10 shadow-accent-900/10 bg-accent-50 dark:border-accent-900 dark:bg-accent-950/20 border',
13
transparent: ''
14
} as Record<string, string>;
15
···
39
bind:this={ref}
40
draggable={isEditing}
41
class={[
42
-
'card group focus-within:outline-accent-500 @container/card absolute z-0 rounded-2xl outline-offset-2 transition-all duration-200 focus-within:outline-2',
43
color ? (colors[color] ?? colors.accent) : colors.base,
44
color !== 'accent' && item.color !== 'base' && item.color !== 'transparent' ? color : '',
45
showOutline ? 'outline-2' : ''
···
60
--columns: ${COLUMNS}`}
61
{...rest}
62
>
63
-
<div class="relative h-full w-full overflow-hidden rounded-[15px] isolate">
64
{@render children?.()}
65
</div>
66
{@render controls?.()}
···
7
import { getColor } from '..';
8
9
const colors = {
10
+
base: 'border-base-300 shadow-lg dark:shadow-none inset-shadow-sm inset-shadow-base-500/10 shadow-base-900/5 bg-base-50 dark:border-base-700 dark:bg-base-900/30 border',
11
accent:
12
+
'border-accent-300 shadow-lg inset-shadow-sm inset-shadow-accent-500/10 shadow-accent-900/10 bg-accent-50 dark:border-accent-900 dark:bg-accent-900/10 border dark:inset-shadow-accent-500/20',
13
transparent: ''
14
} as Record<string, string>;
15
···
39
bind:this={ref}
40
draggable={isEditing}
41
class={[
42
+
'card group focus-within:outline-accent-500 @container/card absolute z-0 rounded-3xl outline-offset-2 transition-all duration-200 focus-within:outline-2 isolate',
43
color ? (colors[color] ?? colors.accent) : colors.base,
44
color !== 'accent' && item.color !== 'base' && item.color !== 'transparent' ? color : '',
45
showOutline ? 'outline-2' : ''
···
60
--columns: ${COLUMNS}`}
61
{...rest}
62
>
63
+
<div class="relative h-full w-full overflow-hidden rounded-[23px] isolate">
64
{@render children?.()}
65
</div>
66
{@render controls?.()}
+77
-16
src/lib/cards/BaseCard/BaseEditingCard.svelte
···
62
const minH = $derived(cardDef.minH ?? (isMobile() ? 2 : 2));
63
64
const maxW = $derived(cardDef.maxW ?? COLUMNS);
65
-
const maxH = $derived(cardDef.maxH ?? 6);
66
67
// Resize handle state
68
let isResizing = $state(false);
···
99
const deltaX = e.clientX - resizeStartX;
100
const deltaY = e.clientY - resizeStartY;
101
102
-
const refRect = ref.getBoundingClientRect();
103
-
104
-
console.log(Math.round(deltaX / cellSize));
105
// Convert pixel delta to grid units (2 grid units = 1 visual cell)
106
const gridDeltaW = Math.round(deltaX / cellSize);
107
const gridDeltaH = Math.round(deltaY / cellSize);
···
135
document.removeEventListener('pointermove', handleResizeMove);
136
document.removeEventListener('pointerup', handleResizeEnd);
137
}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
138
</script>
139
140
<BaseCard {item} {...rest} isEditing={true} bind:ref showOutline={isResizing}>
···
170
171
<div
172
class={[
173
-
'absolute -bottom-7 z-50 w-full items-center justify-center text-xs group-focus-within:inline-flex group-hover:inline-flex',
174
colorPopoverOpen ? 'inline-flex' : 'hidden'
175
]}
176
>
177
<div
178
-
class="bg-base-100 border-base-200 dark:bg-base-800 dark:border-base-700 inline-flex items-center gap-0.5 rounded-2xl border p-1 px-2 shadow-lg"
179
>
180
<Popover bind:open={colorPopoverOpen}>
181
{#snippet child({ props })}
···
216
/>
217
</Popover>
218
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
219
{#if onshowsettings}
220
<button
221
onclick={() => {
···
252
{#if cardDef.canResize !== false}
253
<!-- Resize handle at bottom right corner -->
254
<!-- svelte-ignore a11y_no_static_element_interactions -->
255
-
<div
256
-
class={[
257
-
'absolute pointer-events-none inset-0 z-50 items-end justify-end overflow-hidden rounded-2xl group-focus-within:flex group-hover:flex',
258
-
isResizing ? 'flex' : 'hidden'
259
-
]}
260
-
onpointerdown={handleResizeStart}
261
-
>
262
-
<div onpointerdown={handleResizeStart} class="pointer-events-auto cursor-se-resize">
263
<svg
264
xmlns="http://www.w3.org/2000/svg"
265
viewBox="0 0 24 24"
···
268
stroke-width="2"
269
stroke-linecap="round"
270
stroke-linejoin="round"
271
-
class="text-base-500 size-4"
272
>
273
<circle cx="12" cy="5" r="1" /><circle cx="19" cy="5" r="1" /><circle
274
cx="5"
···
288
</svg>
289
<span class="sr-only">Resize card</span>
290
</div>
291
-
</div>
292
{/if}
293
{/if}
294
{/snippet}
···
62
const minH = $derived(cardDef.minH ?? (isMobile() ? 2 : 2));
63
64
const maxW = $derived(cardDef.maxW ?? COLUMNS);
65
+
const maxH = $derived(cardDef.maxH ?? (isMobile() ? 12 : 6));
66
67
// Resize handle state
68
let isResizing = $state(false);
···
99
const deltaX = e.clientX - resizeStartX;
100
const deltaY = e.clientY - resizeStartY;
101
0
0
0
102
// Convert pixel delta to grid units (2 grid units = 1 visual cell)
103
const gridDeltaW = Math.round(deltaX / cellSize);
104
const gridDeltaH = Math.round(deltaY / cellSize);
···
132
document.removeEventListener('pointermove', handleResizeMove);
133
document.removeEventListener('pointerup', handleResizeEnd);
134
}
135
+
136
+
137
+
function canSetSize(w: number, h: number) {
138
+
if (!cardDef) return false;
139
+
140
+
if(isMobile()) {
141
+
w *= 2;
142
+
h *= 2;
143
+
}
144
+
145
+
return w >= minW && w <= maxW && h >= minH && h <= maxH;
146
+
}
147
+
148
+
function setSize(w: number, h: number) {
149
+
150
+
if(isMobile()) {
151
+
w *= 2;
152
+
h *= 2;
153
+
}
154
+
onsetsize?.(w, h);
155
+
}
156
</script>
157
158
<BaseCard {item} {...rest} isEditing={true} bind:ref showOutline={isResizing}>
···
188
189
<div
190
class={[
191
+
'absolute -bottom-7 w-full items-center justify-center text-xs group-focus-within:inline-flex group-hover:inline-flex',
192
colorPopoverOpen ? 'inline-flex' : 'hidden'
193
]}
194
>
195
<div
196
+
class="bg-base-100 z-[100] border-base-200 dark:bg-base-800 dark:border-base-700 inline-flex items-center gap-0.5 rounded-2xl border p-1 px-2 shadow-lg"
197
>
198
<Popover bind:open={colorPopoverOpen}>
199
{#snippet child({ props })}
···
234
/>
235
</Popover>
236
237
+
238
+
{#if canSetSize(2, 2)}
239
+
<button
240
+
onclick={() => {
241
+
setSize(2, 2);
242
+
}}
243
+
class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2"
244
+
>
245
+
<div class="border-base-900 dark:border-base-50 size-3 rounded-sm border-2"></div>
246
+
247
+
<span class="sr-only">set size to 1x1</span>
248
+
</button>
249
+
{/if}
250
+
251
+
{#if canSetSize(4, 2)}
252
+
<button
253
+
onclick={() => {
254
+
setSize(4, 2);
255
+
}}
256
+
class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2"
257
+
>
258
+
<div class="border-base-900 dark:border-base-50 h-3 w-5 rounded-sm border-2"></div>
259
+
<span class="sr-only">set size to 2x1</span>
260
+
</button>
261
+
{/if}
262
+
{#if canSetSize(2, 4)}
263
+
<button
264
+
onclick={() => {
265
+
setSize(2, 4);
266
+
}}
267
+
class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2"
268
+
>
269
+
<div class="border-base-900 dark:border-base-50 h-5 w-3 rounded-sm border-2"></div>
270
+
271
+
<span class="sr-only">set size to 1x2</span>
272
+
</button>
273
+
{/if}
274
+
{#if canSetSize(4, 4)}
275
+
<button
276
+
onclick={() => {
277
+
setSize(4, 4);
278
+
}}
279
+
class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2"
280
+
>
281
+
<div class="border-base-900 dark:border-base-50 h-5 w-5 rounded-sm border-2"></div>
282
+
283
+
<span class="sr-only">set size to 2x2</span>
284
+
</button>
285
+
{/if}
286
+
287
{#if onshowsettings}
288
<button
289
onclick={() => {
···
320
{#if cardDef.canResize !== false}
321
<!-- Resize handle at bottom right corner -->
322
<!-- svelte-ignore a11y_no_static_element_interactions -->
323
+
324
+
<div onpointerdown={handleResizeStart} class="absolute hidden group-hover:block right-0.5 bottom-0.5 pointer-events-auto cursor-se-resize bg-base-300/70 p-1 dark:bg-base-900/70 rounded-md rounded-br-3xl">
0
0
0
0
0
0
325
<svg
326
xmlns="http://www.w3.org/2000/svg"
327
viewBox="0 0 24 24"
···
330
stroke-width="2"
331
stroke-linecap="round"
332
stroke-linejoin="round"
333
+
class=" dark:text-base-400 text-base-600 size-4"
334
>
335
<circle cx="12" cy="5" r="1" /><circle cx="19" cy="5" r="1" /><circle
336
cx="5"
···
350
</svg>
351
<span class="sr-only">Resize card</span>
352
</div>
0
353
{/if}
354
{/if}
355
{/snippet}
+1
src/lib/cards/BlueskyMediaCard/Video.svelte
···
32
muted
33
loop
34
autoplay
0
35
class="absolute inset-0 h-full w-full object-cover"
36
aria-label={video.alt}
37
></video>
···
32
muted
33
loop
34
autoplay
35
+
playsinline
36
class="absolute inset-0 h-full w-full object-cover"
37
aria-label={video.alt}
38
></video>
+5
-2
src/lib/cards/utils/MarkdownTextEditor.svelte
···
85
onUpdate: () => {
86
update();
87
},
88
-
0
0
89
content: json,
90
91
editorProps: {
92
attributes: {
93
class: 'outline-none'
94
-
}
0
95
}
96
});
97
···
85
onUpdate: () => {
86
update();
87
},
88
+
onDrop: () => {
89
+
return false;
90
+
},
91
content: json,
92
93
editorProps: {
94
attributes: {
95
class: 'outline-none'
96
+
},
97
+
handleDOMEvents: { drop: () => true }
98
}
99
});
100
+10
-5
src/routes/[handle]/og.png/+server.ts
···
22
23
<p class="mt-8 text-4xl text-neutral-300">Check out my blento</p>
24
25
-
<div class="w-20 h-20 rounded-xl bg-rose-700 text-transparent absolute top-70 right-20">h</div>
26
-
<div class="w-20 h-40 rounded-xl bg-rose-400 text-transparent absolute top-94 right-20">h</div>
27
-
<div class="w-40 h-40 rounded-xl bg-rose-300 text-transparent absolute top-70 right-44">h</div>
28
-
<div class="w-20 h-40 rounded-xl bg-rose-600 text-transparent absolute top-70 right-88">h</div>
29
-
<div class="w-40 h-20 rounded-xl bg-rose-500 text-transparent absolute top-114 right-68">h</div>
0
0
0
0
0
30
</div>
31
`;
32
···
22
23
<p class="mt-8 text-4xl text-neutral-300">Check out my blento</p>
24
25
+
<svg class="absolute w-130 h-130 top-50 right-0" viewBox="0 0 900 900" fill="none" xmlns="http://www.w3.org/2000/svg">
26
+
<rect x="100" y="100" width="160" height="340" rx="23" fill="#EF4444"/>
27
+
<rect x="640" y="280" width="160" height="340" rx="23" fill="#22C55E"/>
28
+
<rect x="280" y="100" width="340" height="340" rx="23" fill="#F59E0B"/>
29
+
<rect x="100" y="460" width="340" height="160" rx="23" fill="#0EA5E9"/>
30
+
<rect x="640" y="100" width="160" height="160" rx="23" fill="#EAB308"/>
31
+
<rect x="100" y="640" width="160" height="160" rx="23" fill="#6366F1"/>
32
+
<rect x="460" y="460" width="160" height="160" rx="23" fill="#14B8A6"/>
33
+
<rect x="280" y="640" width="520" height="160" rx="23" fill="#A855F7"/>
34
+
</svg>
35
</div>
36
`;
37