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