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
v0
jycouet
2 weeks ago
42e3b83a
890d1bf9
+369
-27
15 changed files
expand all
collapse all
unified
split
package.json
pnpm-lock.yaml
src
lib
cards
BaseCard
BaseEditingCard.svelte
BigSocialCard
BigSocialCard.svelte
BlueskyProfileCard
BlueskyProfileCard.svelte
GitHubProfileCard
GitHubProfileCard.svelte
ImageCard
ImageCard.svelte
LinkCard
LinkCard.svelte
MapCard
MapCard.svelte
components
qr
QRCodeDisplay.svelte
QRCodeModal.svelte
QRModalProvider.svelte
qrOverlay.svelte.ts
website
Profile.svelte
Website.svelte
+1
package.json
···
73
73
"mapbox-gl": "^3.18.1",
74
74
"marked": "^17.0.1",
75
75
"plyr": "^3.8.4",
76
76
+
"qr-code-styling": "^1.8.6",
76
77
"simple-icons": "^16.6.0",
77
78
"svelte-sonner": "^1.0.7",
78
79
"tailwind-merge": "^3.4.0",
+16
pnpm-lock.yaml
···
110
110
plyr:
111
111
specifier: ^3.8.4
112
112
version: 3.8.4
113
113
+
qr-code-styling:
114
114
+
specifier: ^1.8.6
115
115
+
version: 1.9.2
113
116
simple-icons:
114
117
specifier: ^16.6.0
115
118
version: 16.6.0
···
2484
2487
punycode@2.3.1:
2485
2488
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
2486
2489
engines: {node: '>=6'}
2490
2490
+
2491
2491
+
qr-code-styling@1.9.2:
2492
2492
+
resolution: {integrity: sha512-RgJaZJ1/RrXJ6N0j7a+pdw3zMBmzZU4VN2dtAZf8ZggCfRB5stEQ3IoDNGaNhYY3nnZKYlYSLl5YkfWN5dPutg==}
2493
2493
+
engines: {node: '>=18.18.0'}
2494
2494
+
2495
2495
+
qrcode-generator@1.5.2:
2496
2496
+
resolution: {integrity: sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==}
2487
2497
2488
2498
quickselect@3.0.0:
2489
2499
resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==}
···
5047
5057
punycode.js@2.3.1: {}
5048
5058
5049
5059
punycode@2.3.1: {}
5060
5060
+
5061
5061
+
qr-code-styling@1.9.2:
5062
5062
+
dependencies:
5063
5063
+
qrcode-generator: 1.5.2
5064
5064
+
5065
5065
+
qrcode-generator@1.5.2: {}
5050
5066
5051
5067
quickselect@3.0.0: {}
5052
5068
+4
-4
src/lib/cards/BaseCard/BaseEditingCard.svelte
···
57
57
58
58
const cardDef = $derived(CardDefinitionsByType[item.cardType]);
59
59
60
60
-
const minW = $derived(cardDef.minW ?? (isMobile() ? 2 : 2));
61
61
-
const minH = $derived(cardDef.minH ?? (isMobile() ? 2 : 2));
60
60
+
const minW = $derived(cardDef?.minW ?? (isMobile() ? 2 : 2));
61
61
+
const minH = $derived(cardDef?.minH ?? (isMobile() ? 2 : 2));
62
62
63
63
-
const maxW = $derived(cardDef.maxW ?? COLUMNS);
64
64
-
const maxH = $derived(cardDef.maxH ?? (isMobile() ? 12 : 6));
63
63
+
const maxW = $derived(cardDef?.maxW ?? COLUMNS);
64
64
+
const maxH = $derived(cardDef?.maxH ?? (isMobile() ? 12 : 6));
65
65
66
66
// Resize handle state
67
67
let isResizing = $state(false);
+16
-3
src/lib/cards/BigSocialCard/BigSocialCard.svelte
···
1
1
<script lang="ts">
2
2
import { platformsData } from '.';
3
3
import type { ContentComponentProps } from '../types';
4
4
+
import { qrOverlay } from '$lib/components/qr/qrOverlay.svelte';
4
5
5
6
let { item, isEditing }: ContentComponentProps = $props();
6
7
7
8
const platform = $derived(item.cardData.platform as string);
9
9
+
const platformData = $derived(platformsData[platform]);
8
10
</script>
9
11
10
12
<div
···
14
16
<div
15
17
class="flex aspect-square max-h-full max-w-full items-center justify-center [&_svg]:size-full [&_svg]:max-w-60 [&_svg]:fill-white"
16
18
>
17
17
-
{@html platformsData[platform].svg}
19
19
+
{@html platformData?.svg}
18
20
</div>
19
21
</div>
20
22
21
23
{#if !isEditing}
22
22
-
<a href={item.cardData.href} target="_blank" rel="noopener noreferrer">
24
24
+
<a
25
25
+
href={item.cardData.href}
26
26
+
target="_blank"
27
27
+
rel="noopener noreferrer"
28
28
+
use:qrOverlay={{
29
29
+
context: {
30
30
+
title: platformData?.title,
31
31
+
icon: platformData?.svg,
32
32
+
iconColor: platformData?.hex
33
33
+
}
34
34
+
}}
35
35
+
>
23
36
<div class="absolute inset-0 z-50"></div>
24
24
-
<span class="sr-only">open {platformsData[platform].title}</span>
37
37
+
<span class="sr-only">open {platformData?.title}</span>
25
38
</a>
26
39
{/if}
+13
-3
src/lib/cards/BlueskyProfileCard/BlueskyProfileCard.svelte
···
1
1
<script lang="ts">
2
2
-
import type { Item } from '$lib/types';
2
2
+
import type { ContentComponentProps } from '../types';
3
3
+
import { qrOverlay } from '$lib/components/qr/qrOverlay.svelte';
3
4
4
4
-
let { item }: { item: Item } = $props();
5
5
+
let { item, isEditing }: ContentComponentProps = $props();
6
6
+
7
7
+
const profileUrl = $derived(`https://bsky.app/profile/${item.cardData.handle}`);
5
8
</script>
6
9
7
10
<a
8
11
target="_blank"
9
9
-
href="/{item.cardData.handle}"
12
12
+
href={profileUrl}
10
13
class="flex h-full w-full flex-col items-center justify-center gap-2 rounded-xl p-2 transition-colors duration-150"
14
14
+
use:qrOverlay={{
15
15
+
disabled: isEditing,
16
16
+
context: {
17
17
+
title: item.cardData.displayName || item.cardData.handle,
18
18
+
avatar: item.cardData.avatar
19
19
+
}
20
20
+
}}
11
21
>
12
22
<img
13
23
src={item.cardData.avatar}
+14
-4
src/lib/cards/GitHubProfileCard/GitHubProfileCard.svelte
···
7
7
import GithubContributionsGraph from './GithubContributionsGraph.svelte';
8
8
import { Button } from '@foxui/core';
9
9
import { browser } from '$app/environment';
10
10
+
import { qrOverlay } from '$lib/components/qr/qrOverlay.svelte';
11
11
+
12
12
+
let { item, isEditing }: ContentComponentProps = $props();
10
13
11
11
-
let { item }: ContentComponentProps = $props();
14
14
+
const githubUrl = $derived(`https://github.com/${item.cardData.user}`);
12
15
13
16
const data = getAdditionalUserData();
14
17
···
75
78
</div>
76
79
</div>
77
80
78
78
-
{#if item.cardData.href}
81
81
+
{#if (item.cardData.href || item.cardData.user) && !isEditing}
79
82
<a
80
80
-
href={item.cardData.href}
83
83
+
href={item.cardData.href || githubUrl}
81
84
class="absolute inset-0 h-full w-full"
82
85
target="_blank"
83
86
rel="noopener noreferrer"
87
87
+
use:qrOverlay={{
88
88
+
context: {
89
89
+
title: item.cardData.user,
90
90
+
icon: siGithub.svg,
91
91
+
iconColor: siGithub.hex
92
92
+
}
93
93
+
}}
84
94
>
85
85
-
<span class="sr-only"> Show on github </span>
95
95
+
<span class="sr-only">Show on github</span>
86
96
</a>
87
97
{/if}
+2
src/lib/cards/ImageCard/ImageCard.svelte
···
2
2
import { getDidContext } from '$lib/website/context';
3
3
import { getImageBlobUrl } from '$lib/atproto';
4
4
import type { ContentComponentProps } from '../types';
5
5
+
import { qrOverlay } from '$lib/components/qr/qrOverlay.svelte';
5
6
6
7
let { item = $bindable(), isEditing }: ContentComponentProps = $props();
7
8
···
33
34
class="absolute inset-0 z-50 h-full w-full"
34
35
target="_blank"
35
36
rel="noopener noreferrer"
37
37
+
use:qrOverlay={{ context: { title: item.cardData.hrefText ?? 'Learn more' } }}
36
38
>
37
39
<span class="sr-only">
38
40
{item.cardData.hrefText ?? 'Learn more'}
+9
-2
src/lib/cards/LinkCard/LinkCard.svelte
···
2
2
import { browser } from '$app/environment';
3
3
import { getIsMobile } from '$lib/website/context';
4
4
import type { ContentComponentProps } from '../types';
5
5
+
import { qrOverlay } from '$lib/components/qr/qrOverlay.svelte';
5
6
6
6
-
let { item }: ContentComponentProps = $props();
7
7
+
let { item, isEditing }: ContentComponentProps = $props();
7
8
8
9
let isMobile = getIsMobile();
9
10
···
62
63
alt=""
63
64
/>
64
65
{/if}
65
65
-
{#if item.cardData.href}
66
66
+
{#if item.cardData.href && !isEditing}
66
67
<a
67
68
href={item.cardData.href}
68
69
class="absolute inset-0 h-full w-full"
69
70
target="_blank"
70
71
rel="noopener noreferrer"
72
72
+
use:qrOverlay={{
73
73
+
context: {
74
74
+
title: item.cardData.title,
75
75
+
favicon: item.cardData.favicon
76
76
+
}
77
77
+
}}
71
78
>
72
79
<span class="sr-only">
73
80
{item.cardData.hrefText ?? 'Learn more'}
+8
-2
src/lib/cards/MapCard/MapCard.svelte
···
1
1
<script lang="ts">
2
2
import type { ContentComponentProps } from '../types';
3
3
import Map from './Map.svelte';
4
4
+
import { qrOverlay } from '$lib/components/qr/qrOverlay.svelte';
4
5
5
6
let { item = $bindable(), isEditing }: ContentComponentProps = $props();
7
7
+
8
8
+
const mapsUrl = $derived(
9
9
+
'https://maps.google.com/maps?q=' +
10
10
+
encodeURIComponent(item.cardData.lat + ',' + item.cardData.lon)
11
11
+
);
6
12
</script>
7
13
8
14
<Map bind:item />
···
11
17
<a
12
18
target="_blank"
13
19
rel="noopener noreferrer"
14
14
-
href={'http://maps.google.com/maps?q=' +
15
15
-
encodeURIComponent(item.cardData.lat + ',' + item.cardData.lon)}
20
20
+
href={mapsUrl}
21
21
+
use:qrOverlay={{ context: { title: 'Google Maps' } }}
16
22
>
17
23
<div class="absolute inset-0 z-100"></div>
18
24
<span class="sr-only">open map</span>
+52
src/lib/components/qr/QRCodeDisplay.svelte
···
1
1
+
<script lang="ts">
2
2
+
import { browser } from '$app/environment';
3
3
+
4
4
+
let { url, size = 280, logo }: { url: string; size?: number; logo?: string } = $props();
5
5
+
6
6
+
let container: HTMLDivElement | undefined = $state();
7
7
+
8
8
+
$effect(() => {
9
9
+
if (!browser || !container) return;
10
10
+
11
11
+
const render = async () => {
12
12
+
const QRCodeStylingModule = await import('qr-code-styling');
13
13
+
const QRCodeStyling = QRCodeStylingModule.default;
14
14
+
15
15
+
container!.innerHTML = '';
16
16
+
17
17
+
const options: ConstructorParameters<typeof QRCodeStyling>[0] = {
18
18
+
width: size,
19
19
+
height: size,
20
20
+
data: url,
21
21
+
dotsOptions: {
22
22
+
color: '#000',
23
23
+
type: 'rounded'
24
24
+
},
25
25
+
backgroundOptions: {
26
26
+
color: '#fff'
27
27
+
},
28
28
+
cornersSquareOptions: {
29
29
+
type: 'extra-rounded'
30
30
+
},
31
31
+
cornersDotOptions: {
32
32
+
type: 'dot'
33
33
+
}
34
34
+
};
35
35
+
36
36
+
if (logo) {
37
37
+
options.image = logo;
38
38
+
options.imageOptions = {
39
39
+
crossOrigin: 'anonymous',
40
40
+
margin: 4
41
41
+
};
42
42
+
}
43
43
+
44
44
+
const qrCode = new QRCodeStyling(options);
45
45
+
qrCode.append(container!);
46
46
+
};
47
47
+
48
48
+
render();
49
49
+
});
50
50
+
</script>
51
51
+
52
52
+
<div bind:this={container} class="flex items-center justify-center"></div>
+84
src/lib/components/qr/QRCodeModal.svelte
···
1
1
+
<script lang="ts">
2
2
+
import { Modal, Button, toast } from '@foxui/core';
3
3
+
import QRCodeDisplay from './QRCodeDisplay.svelte';
4
4
+
5
5
+
export type QRContext = {
6
6
+
title?: string;
7
7
+
icon?: string;
8
8
+
iconColor?: string;
9
9
+
favicon?: string;
10
10
+
avatar?: string;
11
11
+
};
12
12
+
13
13
+
let {
14
14
+
open = $bindable(false),
15
15
+
href,
16
16
+
context = {}
17
17
+
}: {
18
18
+
open: boolean;
19
19
+
href: string;
20
20
+
context?: QRContext;
21
21
+
} = $props();
22
22
+
23
23
+
async function copyUrl() {
24
24
+
try {
25
25
+
await navigator.clipboard.writeText(href);
26
26
+
toast.success('URL copied!');
27
27
+
} catch {
28
28
+
toast.error('Failed to copy');
29
29
+
}
30
30
+
}
31
31
+
32
32
+
const logoUrl = $derived(context.avatar || context.favicon);
33
33
+
</script>
34
34
+
35
35
+
<Modal bind:open closeButton={true} class="max-w-sm">
36
36
+
<div class="flex flex-col items-center gap-4 p-2">
37
37
+
{#if context.icon}
38
38
+
<div
39
39
+
class="flex size-14 items-center justify-center rounded-2xl [&_svg]:size-8 [&_svg]:fill-white"
40
40
+
style:background-color={context.iconColor ? `#${context.iconColor}` : '#000'}
41
41
+
>
42
42
+
{@html context.icon}
43
43
+
</div>
44
44
+
{:else if context.avatar}
45
45
+
<img src={context.avatar} alt="" class="size-14 rounded-full object-cover" />
46
46
+
{:else if context.favicon}
47
47
+
<img src={context.favicon} alt="" class="size-10 rounded-lg object-cover" />
48
48
+
{/if}
49
49
+
50
50
+
{#if context.title}
51
51
+
<div class="text-base-900 dark:text-base-100 text-lg font-semibold">
52
52
+
{context.title}
53
53
+
</div>
54
54
+
{/if}
55
55
+
56
56
+
<div class="overflow-hidden rounded-2xl">
57
57
+
<QRCodeDisplay url={href} size={280} logo={logoUrl} />
58
58
+
</div>
59
59
+
60
60
+
<div class="flex w-full items-center gap-2">
61
61
+
<div
62
62
+
class="bg-base-100 dark:bg-base-800 text-base-600 dark:text-base-400 flex-1 truncate rounded-lg px-3 py-2 text-sm"
63
63
+
>
64
64
+
{href}
65
65
+
</div>
66
66
+
<Button onclick={copyUrl} variant="ghost" size="sm">
67
67
+
<svg
68
68
+
xmlns="http://www.w3.org/2000/svg"
69
69
+
fill="none"
70
70
+
viewBox="0 0 24 24"
71
71
+
stroke-width="1.5"
72
72
+
stroke="currentColor"
73
73
+
class="size-4"
74
74
+
>
75
75
+
<path
76
76
+
stroke-linecap="round"
77
77
+
stroke-linejoin="round"
78
78
+
d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9.75a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184"
79
79
+
/>
80
80
+
</svg>
81
81
+
</Button>
82
82
+
</div>
83
83
+
</div>
84
84
+
</Modal>
+25
src/lib/components/qr/QRModalProvider.svelte
···
1
1
+
<script lang="ts">
2
2
+
import { onMount, onDestroy } from 'svelte';
3
3
+
import QRCodeModal, { type QRContext } from './QRCodeModal.svelte';
4
4
+
import { registerQRModal, unregisterQRModal } from './qrOverlay.svelte';
5
5
+
6
6
+
let open = $state(false);
7
7
+
let href = $state('');
8
8
+
let context = $state<QRContext>({});
9
9
+
10
10
+
function showModal(newHref: string, newContext: QRContext) {
11
11
+
href = newHref;
12
12
+
context = newContext;
13
13
+
open = true;
14
14
+
}
15
15
+
16
16
+
onMount(() => {
17
17
+
registerQRModal(showModal);
18
18
+
});
19
19
+
20
20
+
onDestroy(() => {
21
21
+
unregisterQRModal();
22
22
+
});
23
23
+
</script>
24
24
+
25
25
+
<QRCodeModal bind:open {href} {context} />
+72
src/lib/components/qr/qrOverlay.svelte.ts
···
1
1
+
import type { QRContext } from './QRCodeModal.svelte';
2
2
+
3
3
+
// Global state for QR modal
4
4
+
let openModal: ((href: string, context: QRContext) => void) | null = null;
5
5
+
6
6
+
export function registerQRModal(fn: (href: string, context: QRContext) => void) {
7
7
+
openModal = fn;
8
8
+
}
9
9
+
10
10
+
export function unregisterQRModal() {
11
11
+
openModal = null;
12
12
+
}
13
13
+
14
14
+
export function qrOverlay(
15
15
+
node: HTMLAnchorElement,
16
16
+
params: { context?: QRContext; disabled?: boolean } = {}
17
17
+
) {
18
18
+
const LONG_PRESS_DURATION = 500;
19
19
+
let longPressTimer: ReturnType<typeof setTimeout> | null = null;
20
20
+
let isLongPress = false;
21
21
+
22
22
+
function startLongPress() {
23
23
+
if (params.disabled) return;
24
24
+
isLongPress = false;
25
25
+
longPressTimer = setTimeout(() => {
26
26
+
isLongPress = true;
27
27
+
openModal?.(node.href, params.context ?? {});
28
28
+
}, LONG_PRESS_DURATION);
29
29
+
}
30
30
+
31
31
+
function cancelLongPress() {
32
32
+
if (longPressTimer) {
33
33
+
clearTimeout(longPressTimer);
34
34
+
longPressTimer = null;
35
35
+
}
36
36
+
}
37
37
+
38
38
+
function handleClick(e: MouseEvent) {
39
39
+
if (isLongPress) {
40
40
+
e.preventDefault();
41
41
+
isLongPress = false;
42
42
+
}
43
43
+
}
44
44
+
45
45
+
function handleContextMenu(e: MouseEvent) {
46
46
+
if (params.disabled) return;
47
47
+
e.preventDefault();
48
48
+
openModal?.(node.href, params.context ?? {});
49
49
+
}
50
50
+
51
51
+
node.addEventListener('pointerdown', startLongPress);
52
52
+
node.addEventListener('pointerup', cancelLongPress);
53
53
+
node.addEventListener('pointercancel', cancelLongPress);
54
54
+
node.addEventListener('pointerleave', cancelLongPress);
55
55
+
node.addEventListener('click', handleClick);
56
56
+
node.addEventListener('contextmenu', handleContextMenu);
57
57
+
58
58
+
return {
59
59
+
update(newParams: { context?: QRContext; disabled?: boolean }) {
60
60
+
params = newParams;
61
61
+
},
62
62
+
destroy() {
63
63
+
node.removeEventListener('pointerdown', startLongPress);
64
64
+
node.removeEventListener('pointerup', cancelLongPress);
65
65
+
node.removeEventListener('pointercancel', cancelLongPress);
66
66
+
node.removeEventListener('pointerleave', cancelLongPress);
67
67
+
node.removeEventListener('click', handleClick);
68
68
+
node.removeEventListener('contextmenu', handleContextMenu);
69
69
+
cancelLongPress();
70
70
+
}
71
71
+
};
72
72
+
}
+51
-9
src/lib/website/Profile.svelte
···
8
8
import { getDescription, getName } from '$lib/helper';
9
9
import { page } from '$app/state';
10
10
import type { ActorIdentifier } from '@atcute/lexicons';
11
11
+
import QRCodeModal from '$lib/components/qr/QRCodeModal.svelte';
11
12
12
13
let {
13
14
data,
···
20
21
const renderer = new marked.Renderer();
21
22
renderer.link = ({ href, title, text }) =>
22
23
`<a target="_blank" href="${href}" title="${title}">${text}</a>`;
24
24
+
25
25
+
let qrOpen = $state(false);
26
26
+
let longPressTimer: ReturnType<typeof setTimeout> | null = null;
27
27
+
28
28
+
const profileUrl = $derived(`${page.url}/${data.handle}`);
29
29
+
30
30
+
function startLongPress() {
31
31
+
longPressTimer = setTimeout(() => {
32
32
+
qrOpen = true;
33
33
+
}, 500);
34
34
+
}
35
35
+
36
36
+
function cancelLongPress() {
37
37
+
if (longPressTimer) {
38
38
+
clearTimeout(longPressTimer);
39
39
+
longPressTimer = null;
40
40
+
}
41
41
+
}
42
42
+
43
43
+
function handleContextMenu(e: MouseEvent) {
44
44
+
e.preventDefault();
45
45
+
qrOpen = true;
46
46
+
}
23
47
</script>
24
48
25
49
<!-- lg:fixed lg:h-screen lg:w-1/4 lg:max-w-none lg:px-12 lg:pt-24 xl:w-1/3 -->
···
27
51
class="mx-auto flex max-w-lg flex-col justify-between px-8 @5xl/wrapper:fixed @5xl/wrapper:h-screen @5xl/wrapper:w-1/4 @5xl/wrapper:max-w-none @5xl/wrapper:px-12"
28
52
>
29
53
<div class="flex flex-col gap-4 pt-16 pb-8 @5xl/wrapper:h-screen @5xl/wrapper:pt-24">
30
30
-
{#if data.profile.avatar}
31
31
-
<img
32
32
-
class="border-base-400 dark:border-base-800 size-32 rounded-full border @5xl/wrapper:size-44"
33
33
-
src={data.profile.avatar}
34
34
-
alt=""
35
35
-
/>
36
36
-
{:else}
37
37
-
<div class="bg-base-300 dark:bg-base-700 size-32 rounded-full @5xl/wrapper:size-44"></div>
38
38
-
{/if}
54
54
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
55
55
+
<div
56
56
+
class="w-fit cursor-pointer"
57
57
+
onpointerdown={startLongPress}
58
58
+
onpointerup={cancelLongPress}
59
59
+
onpointercancel={cancelLongPress}
60
60
+
onpointerleave={cancelLongPress}
61
61
+
oncontextmenu={handleContextMenu}
62
62
+
>
63
63
+
{#if data.profile.avatar}
64
64
+
<img
65
65
+
class="border-base-400 dark:border-base-800 size-32 rounded-full border @5xl/wrapper:size-44"
66
66
+
src={data.profile.avatar}
67
67
+
alt=""
68
68
+
/>
69
69
+
{:else}
70
70
+
<div class="bg-base-300 dark:bg-base-700 size-32 rounded-full @5xl/wrapper:size-44"></div>
71
71
+
{/if}
72
72
+
</div>
73
73
+
<QRCodeModal
74
74
+
bind:open={qrOpen}
75
75
+
href={profileUrl}
76
76
+
context={{
77
77
+
title: getName(data),
78
78
+
avatar: data.profile.avatar
79
79
+
}}
80
80
+
/>
39
81
40
82
<div class="text-4xl font-bold wrap-anywhere">
41
83
{getName(data)}
+2
src/lib/website/Website.svelte
···
9
9
import Context from './Context.svelte';
10
10
import Head from './Head.svelte';
11
11
import type { Did, Handle } from '@atcute/lexicons';
12
12
+
import QRModalProvider from '$lib/components/qr/QRModalProvider.svelte';
12
13
13
14
let { data }: { data: WebsiteData } = $props();
14
15
···
38
39
/>
39
40
40
41
<Context {data}>
42
42
+
<QRModalProvider />
41
43
<div class="@container/wrapper relative w-full">
42
44
{#if !getHideProfileSection(data)}
43
45
<Profile {data} showEditButton={true} />