Skip to content

Commit 300bc6f

Browse files
committed
feat: dynamic footer with AP + Bluesky handles, instance picker, favicon fixes
- Footer shows AP bot handle (Mastodon icon) + Bluesky handle (butterfly icon) - Both read from env vars (BOT_USERNAME, BLUESKY_HANDLE), not hardcoded - AP link opens instance picker modal for easy follow (reuses authorize_interaction) - Bluesky link opens bsky.app profile - Generated favicon.ico (32x32) and apple-touch-icon.png (180x180) - Added icon links in app.html to fix 404s
1 parent 1d521e6 commit 300bc6f

5 files changed

Lines changed: 109 additions & 5 deletions

File tree

src/app.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
<meta charset="utf-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
66
<link rel="icon" type="image/png" href="/favicon.png" />
7+
<link rel="icon" href="/favicon.ico" sizes="32x32" />
8+
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
79
%sveltekit.head%
810
</head>
911
<body data-sveltekit-preload-data="hover">

src/routes/+layout.server.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ export const load: LayoutServerLoad = () => {
77
const botHandle = host ? `@${username}@${host}` : '';
88
const botActorUrl = host ? `${origin}/ap/actor/${username}` : '';
99

10-
return { botHandle, botActorUrl };
10+
const bskyHandle = process.env.BLUESKY_HANDLE || '';
11+
const bskyProfileUrl = bskyHandle ? `https://bsky.app/profile/${bskyHandle}` : '';
12+
13+
return { botHandle, botActorUrl, bskyHandle, bskyProfileUrl };
1114
};

src/routes/+layout.svelte

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,36 @@
11
<script>
2+
import { browser } from '$app/environment';
23
import '../app.css';
34
let { data, children } = $props();
4-
const { botHandle, botActorUrl } = data;
5+
const { botHandle, botActorUrl, bskyHandle, bskyProfileUrl } = data;
6+
7+
let instanceModalOpen = $state(false);
8+
let instanceInput = $state('');
9+
10+
$effect(() => {
11+
if (browser) {
12+
instanceInput = localStorage.getItem('fedi-instance') || '';
13+
}
14+
});
15+
16+
function openOnFediverse(e) {
17+
e.preventDefault();
18+
if (!botActorUrl) return;
19+
const saved = browser && localStorage.getItem('fedi-instance');
20+
if (saved) {
21+
window.open(`https://${saved}/authorize_interaction?uri=${encodeURIComponent(botActorUrl)}`, '_blank');
22+
} else {
23+
instanceModalOpen = true;
24+
}
25+
}
26+
27+
function submitInstance() {
28+
const instance = instanceInput.trim().replace(/^https?:\/\//, '').replace(/\/+$/, '');
29+
if (!instance) return;
30+
localStorage.setItem('fedi-instance', instance);
31+
instanceModalOpen = false;
32+
window.open(`https://${instance}/authorize_interaction?uri=${encodeURIComponent(botActorUrl)}`, '_blank');
33+
}
534
</script>
635

736
<svelte:head>
@@ -28,12 +57,53 @@
2857
</main>
2958

3059
<footer class="container">
31-
{#if botHandle}
32-
<p>Follow on the fediverse: <a href={botActorUrl} rel="me">{botHandle}</a></p>
33-
{/if}
60+
<div class="follow-links">
61+
{#if botHandle}
62+
<a href={botActorUrl} rel="me" class="follow-link" onclick={openOnFediverse} title="Follow on the fediverse">
63+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 74 79" fill="currentColor">
64+
<path d="M73.7 17.7c-1-6.2-6.3-11-12.7-12.3C54.5 4 37.2 2 37.2 2h-.1C27 2 19.5 4 13 5.4 6.6 6.7 1.3 11.5.3 17.7c-.5 3.3-.9 7.1-.9 11 0 3.2.1 6.2.3 9 .7 9.4 5.3 17.7 13.4 21.4 3.8 1.8 8.1 2.7 12.5 3 4.5.3 8.7-.3 12.5-1.7 0 0 0 0 0 0-.3-1.4-.6-2.9-1-4.3-4 1.2-8.3 1.6-12.6 1.3-4.3-.3-8.5-1.7-8.8-6.5-.1-.5-.1-1-.1-1.5 4 1 8.1 1.6 12.3 1.8 2.6.1 5.1 0 7.6-.2 7.1-.7 13.3-2.9 14.1-5.3.6-1.8.8-3.8.8-5.9v-.3c0-6.3 2.4-7.2 2.4-7.2 2.4-1.1 1.3 4.4 1.3 7.2 0 2.4-.3 5.5-1.1 8.5-.5 2-1.4 3.8-2.5 5.4 4.1-1.7 7.5-4.6 9.5-8.5 2.5-5 2.8-10.9 2.8-15.9 0-3.9-.3-7.7-.9-11z"/>
65+
<path d="M61.2 27.2v22.7h-9V28c0-4.6-1.9-7-5.8-7-4.3 0-6.4 2.8-6.4 8.3v12h-9V29.3c0-5.5-2.2-8.3-6.4-8.3-3.9 0-5.8 2.3-5.8 7v21.9h-9V27.2c0-4.6 1.2-8.3 3.5-11 2.4-2.7 5.6-4.1 9.5-4.1 4.5 0 7.9 1.7 10.2 5.2l2.2 3.7 2.2-3.7c2.2-3.5 5.7-5.2 10.2-5.2 3.9 0 7.1 1.4 9.5 4.1 2.3 2.7 3.5 6.4 3.5 11z"/>
66+
</svg>
67+
{botHandle}
68+
</a>
69+
{/if}
70+
{#if bskyHandle}
71+
<a href={bskyProfileUrl} target="_blank" rel="noopener" class="follow-link" title="Follow on Bluesky">
72+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 568 501" fill="currentColor">
73+
<path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.889-129.52 80.986-149.07-65.72 11.185-139.6-7.295-159.875-79.748C10.945 203.659 1 75.291 1 57.946 1-28.906 77.135-1.612 123.121 33.664z"/>
74+
</svg>
75+
@{bskyHandle}
76+
</a>
77+
{/if}
78+
</div>
3479
<p><a href="https://github.com/rmdes/newsdiff">Source code</a> · <a href="/feed.xml">Atom feed</a></p>
3580
</footer>
3681

82+
<!-- Fediverse instance picker modal (shared across pages) -->
83+
{#if instanceModalOpen}
84+
<div class="modal-backdrop" onclick={() => instanceModalOpen = false}>
85+
<div class="modal" onclick={(e) => e.stopPropagation()}>
86+
<h3>Your Mastodon instance</h3>
87+
<p>Enter your instance to follow the bot from your account.</p>
88+
<form onsubmit={(e) => { e.preventDefault(); submitInstance(); }}>
89+
<div class="instance-input">
90+
<span class="instance-prefix">https://</span>
91+
<input
92+
type="text"
93+
bind:value={instanceInput}
94+
placeholder="mastodon.social"
95+
autofocus
96+
/>
97+
</div>
98+
<div class="modal-actions">
99+
<button type="button" class="btn-cancel" onclick={() => instanceModalOpen = false}>Cancel</button>
100+
<button type="submit" class="btn-go">Follow on fediverse</button>
101+
</div>
102+
</form>
103+
</div>
104+
</div>
105+
{/if}
106+
37107
<style>
38108
header { border-bottom: 1px solid var(--color-border); margin-bottom: 2rem; }
39109
.header-inner { display: flex; justify-content: space-between; align-items: center; padding-top: 1rem; padding-bottom: 1rem; }
@@ -43,7 +113,36 @@
43113
.nav-links { display: flex; gap: 1.25rem; }
44114
.nav-links a { color: var(--color-muted); text-decoration: none; font-size: 1rem; }
45115
.nav-links a:hover { color: var(--color-primary); }
116+
46117
footer { margin-top: 3rem; padding: 1.5rem 0; border-top: 1px solid var(--color-border); font-size: 0.8rem; color: var(--color-muted); text-align: center; }
47118
footer a { color: var(--color-primary); text-decoration: none; }
48119
footer a:hover { text-decoration: underline; }
120+
121+
.follow-links { display: flex; justify-content: center; gap: 1.5rem; flex-wrap: wrap; margin-bottom: 0.75rem; }
122+
.follow-link { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; color: var(--color-text); text-decoration: none; }
123+
.follow-link:hover { color: var(--color-primary); }
124+
.follow-link svg { opacity: 0.7; }
125+
.follow-link:hover svg { opacity: 1; }
126+
127+
/* Instance picker modal */
128+
.modal-backdrop {
129+
position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 100;
130+
display: flex; align-items: center; justify-content: center;
131+
}
132+
.modal {
133+
background: white; border-radius: 0.5rem; padding: 1.5rem; max-width: 420px; width: 90%;
134+
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
135+
}
136+
.modal h3 { margin: 0 0 0.5rem; font-size: 1.1rem; }
137+
.modal p { font-size: 0.85rem; color: var(--color-muted); margin-bottom: 1rem; }
138+
.instance-input {
139+
display: flex; align-items: center; border: 1px solid var(--color-border); border-radius: 0.25rem;
140+
overflow: hidden; margin-bottom: 1rem;
141+
}
142+
.instance-prefix { padding: 0.5rem; background: #f5f5f5; color: var(--color-muted); font-size: 0.85rem; border-right: 1px solid var(--color-border); }
143+
.instance-input input { flex: 1; border: none; padding: 0.5rem; font-size: 0.9rem; outline: none; }
144+
.modal-actions { display: flex; gap: 0.5rem; justify-content: flex-end; }
145+
.btn-cancel { padding: 0.4rem 0.75rem; border: 1px solid var(--color-border); border-radius: 0.25rem; background: white; cursor: pointer; font-size: 0.85rem; }
146+
.btn-go { padding: 0.4rem 0.75rem; border: none; border-radius: 0.25rem; background: var(--color-primary); color: white; cursor: pointer; font-size: 0.85rem; }
147+
.btn-go:hover { background: #1d4ed8; }
49148
</style>

static/apple-touch-icon.png

2.56 KB
Loading

static/favicon.ico

474 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)