nandi/gleanpublic Fork 0
c177ffe
Commits
Clone
git clone https://git.rickub.com/nandi/glean.git
git clone ssh://git@rickub.com/nandi/glean.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

feat(seo): add Seo component and robots.txtUnverified

Julien Robert committed 2026-08-17T23:31:41+02:00 Browse files
c177ffe parent: 9010e40
added web/src/lib/components/Seo.svelte +56 -0
new file mode 100644
@@ -0,0 +1,56 @@
1+<script lang="ts">
2+ import { page } from "$app/state";
3+
4+ interface Props {
5+ title?: string;
6+ description?: string;
7+ type?: string;
8+ noindex?: boolean;
9+ }
10+
11+ let {
12+ title,
13+ description,
14+ type = "website",
15+ noindex = false,
16+ }: Props = $props();
17+
18+ const siteName = "Glean";
19+ const pageTitle = $derived(
20+ title ? `${title}${siteName}` : `${siteName} — The social RSS reader`,
21+ );
22+ const canonical = $derived(page.url.origin + page.url.pathname);
23+ const absoluteImage = $derived(new URL("/banner.png", page.url).href);
24+ const plainDescription = $derived(
25+ (description ?? "")
26+ .replace(/<[^>]*>/g, " ")
27+ .replace(/\s+/g, " ")
28+ .trim()
29+ .slice(0, 200),
30+ );
31+</script>
32+
33+<svelte:head>
34+ <title>{pageTitle}</title>
35+ {#if plainDescription}
36+ <meta name="description" content={plainDescription} />
37+ {/if}
38+ <link rel="canonical" href={canonical} />
39+ {#if noindex}
40+ <meta name="robots" content="noindex" />
41+ {/if}
42+ <meta property="og:site_name" content={siteName} />
43+ <meta property="og:title" content={pageTitle} />
44+ {#if plainDescription}
45+ <meta property="og:description" content={plainDescription} />
46+ {/if}
47+ <meta property="og:type" content={type} />
48+ <meta property="og:url" content={canonical} />
49+ <meta property="og:image" content={absoluteImage} />
50+ <meta name="twitter:card" content="summary_large_image" />
51+ <meta name="twitter:title" content={pageTitle} />
52+ {#if plainDescription}
53+ <meta name="twitter:description" content={plainDescription} />
54+ {/if}
55+ <meta name="twitter:image" content={absoluteImage} />
56+</svelte:head>
new file mode 100644
@@ -0,0 +1,56 @@
1+<script lang="ts">
2+ import { page } from "$app/state";
3+
4+ interface Props {
5+ title?: string;
6+ description?: string;
7+ type?: string;
8+ noindex?: boolean;
9+ }
10+
11+ let {
12+ title,
13+ description,
14+ type = "website",
15+ noindex = false,
16+ }: Props = $props();
17+
18+ const siteName = "Glean";
19+ const pageTitle = $derived(
20+ title ? `${title}${siteName}` : `${siteName} — The social RSS reader`,
21+ );
22+ const canonical = $derived(page.url.origin + page.url.pathname);
23+ const absoluteImage = $derived(new URL("/banner.png", page.url).href);
24+ const plainDescription = $derived(
25+ (description ?? "")
26+ .replace(/<[^>]*>/g, " ")
27+ .replace(/\s+/g, " ")
28+ .trim()
29+ .slice(0, 200),
30+ );
31+</script>
32+
33+<svelte:head>
34+ <title>{pageTitle}</title>
35+ {#if plainDescription}
36+ <meta name="description" content={plainDescription} />
37+ {/if}
38+ <link rel="canonical" href={canonical} />
39+ {#if noindex}
40+ <meta name="robots" content="noindex" />
41+ {/if}
42+ <meta property="og:site_name" content={siteName} />
43+ <meta property="og:title" content={pageTitle} />
44+ {#if plainDescription}
45+ <meta property="og:description" content={plainDescription} />
46+ {/if}
47+ <meta property="og:type" content={type} />
48+ <meta property="og:url" content={canonical} />
49+ <meta property="og:image" content={absoluteImage} />
50+ <meta name="twitter:card" content="summary_large_image" />
51+ <meta name="twitter:title" content={pageTitle} />
52+ {#if plainDescription}
53+ <meta name="twitter:description" content={plainDescription} />
54+ {/if}
55+ <meta name="twitter:image" content={absoluteImage} />
56+</svelte:head>
modified web/src/routes/+error.svelte +6 -0
@@ -1,8 +1,14 @@
11 <script lang="ts">
22 import { page } from "$app/state";
33 import Logo from "$lib/components/Logo.svelte";
4+ import Seo from "$lib/components/Seo.svelte";
45 </script>
56
7+<Seo
8+ title={page.status === 404 ? "Not found" : "Error " + page.status}
9+ noindex
10+/>
11+
612 <div class="flex min-h-screen flex-col items-center justify-center px-4 py-12">
713 <div class="w-full max-w-sm text-center">
814 <div class="mb-8 flex justify-center"><Logo size="lg" /></div>
@@ -1,8 +1,14 @@
1 <script lang="ts">1 <script lang="ts">
2 import { page } from "$app/state";2 import { page } from "$app/state";
3 import Logo from "$lib/components/Logo.svelte";3 import Logo from "$lib/components/Logo.svelte";
4+ import Seo from "$lib/components/Seo.svelte";
4 </script>5 </script>
5 6
7+<Seo
8+ title={page.status === 404 ? "Not found" : "Error " + page.status}
9+ noindex
10+/>
11+
6 <div class="flex min-h-screen flex-col items-center justify-center px-4 py-12">12 <div class="flex min-h-screen flex-col items-center justify-center px-4 py-12">
7 <div class="w-full max-w-sm text-center">13 <div class="w-full max-w-sm text-center">
8 <div class="mb-8 flex justify-center"><Logo size="lg" /></div>14 <div class="mb-8 flex justify-center"><Logo size="lg" /></div>
modified web/src/routes/+layout.svelte +0 -8
@@ -121,14 +121,6 @@
121121 </script>
122122
123123 <svelte:head>
124- <title>Glean</title>
125- <meta property="og:title" content="Glean" />
126- <meta
127- property="og:description"
128- content="The social RSS reader built on AT Protocol."
129- />
130- <meta property="og:image" content="/banner.png" />
131- <meta property="og:type" content="website" />
132124 <meta name="theme-color" content="#00754A" />
133125 <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
134126 <link rel="manifest" href="/manifest.json" />
@@ -121,14 +121,6 @@
121 </script>121 </script>
122 122
123 <svelte:head>123 <svelte:head>
124- <title>Glean</title>
125- <meta property="og:title" content="Glean" />
126- <meta
127- property="og:description"
128- content="The social RSS reader built on AT Protocol."
129- />
130- <meta property="og:image" content="/banner.png" />
131- <meta property="og:type" content="website" />
132 <meta name="theme-color" content="#00754A" />124 <meta name="theme-color" content="#00754A" />
133 <link rel="icon" type="image/svg+xml" href="/favicon.svg" />125 <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
134 <link rel="manifest" href="/manifest.json" />126 <link rel="manifest" href="/manifest.json" />
modified web/src/routes/+page.svelte +5 -0
@@ -2,8 +2,13 @@
22 // Landing page is static; auth gating happens in +page.server.ts.
33 import Logo from "$lib/components/Logo.svelte";
44 import Icon from "$lib/components/Icon.svelte";
5+ import Seo from "$lib/components/Seo.svelte";
56 </script>
67
8+<Seo
9+ description="The social RSS reader built on AT Protocol. Read your feeds, see what your circle reads, and discover new sources through personalized recommendations."
10+/>
11+
712 <!-- HERO -->
813 <section class="border-b-2 border-[var(--border)]">
914 <div
@@ -2,8 +2,13 @@
2 // Landing page is static; auth gating happens in +page.server.ts.2 // Landing page is static; auth gating happens in +page.server.ts.
3 import Logo from "$lib/components/Logo.svelte";3 import Logo from "$lib/components/Logo.svelte";
4 import Icon from "$lib/components/Icon.svelte";4 import Icon from "$lib/components/Icon.svelte";
5+ import Seo from "$lib/components/Seo.svelte";
5 </script>6 </script>
6 7
8+<Seo
9+ description="The social RSS reader built on AT Protocol. Read your feeds, see what your circle reads, and discover new sources through personalized recommendations."
10+/>
11+
7 <!-- HERO -->12 <!-- HERO -->
8 <section class="border-b-2 border-[var(--border)]">13 <section class="border-b-2 border-[var(--border)]">
9 <div14 <div
modified web/src/routes/articles/+page.svelte +3 -0
@@ -7,6 +7,7 @@
77 import EmptyState from "$lib/components/EmptyState.svelte";
88 import Icon from "$lib/components/Icon.svelte";
99 import NewArticlesBanner from "$lib/components/NewArticlesBanner.svelte";
10+ import Seo from "$lib/components/Seo.svelte";
1011 import { endpoints } from "$lib/api";
1112
1213 let { data }: { data: PageData } = $props();
@@ -88,6 +89,8 @@
8889 }
8990 </script>
9091
92+<Seo title={data.feed?.title || "Articles"} />
93+
9194 {#if data.feed}
9295 <!-- Feed header -->
9396 <div class="mb-6">
@@ -7,6 +7,7 @@
7 import EmptyState from "$lib/components/EmptyState.svelte";7 import EmptyState from "$lib/components/EmptyState.svelte";
8 import Icon from "$lib/components/Icon.svelte";8 import Icon from "$lib/components/Icon.svelte";
9 import NewArticlesBanner from "$lib/components/NewArticlesBanner.svelte";9 import NewArticlesBanner from "$lib/components/NewArticlesBanner.svelte";
10+ import Seo from "$lib/components/Seo.svelte";
10 import { endpoints } from "$lib/api";11 import { endpoints } from "$lib/api";
11 12
12 let { data }: { data: PageData } = $props();13 let { data }: { data: PageData } = $props();
@@ -88,6 +89,8 @@
88 }89 }
89 </script>90 </script>
90 91
92+<Seo title={data.feed?.title || "Articles"} />
93+
91 {#if data.feed}94 {#if data.feed}
92 <!-- Feed header -->95 <!-- Feed header -->
93 <div class="mb-6">96 <div class="mb-6">
modified web/src/routes/articles/[id]/+page.svelte +7 -0
@@ -4,6 +4,7 @@
44 import Favicon from "$lib/components/Favicon.svelte";
55 import AnnotationCard from "$lib/components/AnnotationCard.svelte";
66 import Icon from "$lib/components/Icon.svelte";
7+ import Seo from "$lib/components/Seo.svelte";
78 import BlueskyLogo from "$lib/components/BlueskyLogo.svelte";
89 import { endpoints } from "$lib/api";
910 import { youtubeID, isEmbedURL } from "$lib/format";
@@ -451,6 +452,12 @@
451452 }
452453 </script>
453454
455+<Seo
456+ title={data.article.title}
457+ description={data.article.summary}
458+ type="article"
459+/>
460+
454461 <svelte:window
455462 onmouseup={onMouseUp}
456463 onkeydown={onKeydown}
@@ -4,6 +4,7 @@
4 import Favicon from "$lib/components/Favicon.svelte";4 import Favicon from "$lib/components/Favicon.svelte";
5 import AnnotationCard from "$lib/components/AnnotationCard.svelte";5 import AnnotationCard from "$lib/components/AnnotationCard.svelte";
6 import Icon from "$lib/components/Icon.svelte";6 import Icon from "$lib/components/Icon.svelte";
7+ import Seo from "$lib/components/Seo.svelte";
7 import BlueskyLogo from "$lib/components/BlueskyLogo.svelte";8 import BlueskyLogo from "$lib/components/BlueskyLogo.svelte";
8 import { endpoints } from "$lib/api";9 import { endpoints } from "$lib/api";
9 import { youtubeID, isEmbedURL } from "$lib/format";10 import { youtubeID, isEmbedURL } from "$lib/format";
@@ -451,6 +452,12 @@
451 }452 }
452 </script>453 </script>
453 454
455+<Seo
456+ title={data.article.title}
457+ description={data.article.summary}
458+ type="article"
459+/>
460+
454 <svelte:window461 <svelte:window
455 onmouseup={onMouseUp}462 onmouseup={onMouseUp}
456 onkeydown={onKeydown}463 onkeydown={onKeydown}
modified web/src/routes/auth/login/+page.svelte +6 -0
@@ -1,6 +1,7 @@
11 <script lang="ts">
22 import { endpoints } from "$lib/api";
33 import Logo from "$lib/components/Logo.svelte";
4+ import Seo from "$lib/components/Seo.svelte";
45 import type { Actor } from "$lib/types";
56
67 let handle = $state("");
@@ -70,6 +71,11 @@
7071 }
7172 </script>
7273
74+<Seo
75+ title="Sign in"
76+ description="Sign in to Glean with your Bluesky handle or any AT Protocol account."
77+/>
78+
7379 <div class="flex min-h-screen items-center justify-center px-4 py-12">
7480 <div class="w-full max-w-sm">
7581 <div class="mb-8 flex justify-center">
@@ -1,6 +1,7 @@
1 <script lang="ts">1 <script lang="ts">
2 import { endpoints } from "$lib/api";2 import { endpoints } from "$lib/api";
3 import Logo from "$lib/components/Logo.svelte";3 import Logo from "$lib/components/Logo.svelte";
4+ import Seo from "$lib/components/Seo.svelte";
4 import type { Actor } from "$lib/types";5 import type { Actor } from "$lib/types";
5 6
6 let handle = $state("");7 let handle = $state("");
@@ -70,6 +71,11 @@
70 }71 }
71 </script>72 </script>
72 73
74+<Seo
75+ title="Sign in"
76+ description="Sign in to Glean with your Bluesky handle or any AT Protocol account."
77+/>
78+
73 <div class="flex min-h-screen items-center justify-center px-4 py-12">79 <div class="flex min-h-screen items-center justify-center px-4 py-12">
74 <div class="w-full max-w-sm">80 <div class="w-full max-w-sm">
75 <div class="mb-8 flex justify-center">81 <div class="mb-8 flex justify-center">
modified web/src/routes/dashboard/+page.svelte +3 -0
@@ -7,6 +7,7 @@
77 import EmptyState from "$lib/components/EmptyState.svelte";
88 import Icon from "$lib/components/Icon.svelte";
99 import NewArticlesBanner from "$lib/components/NewArticlesBanner.svelte";
10+ import Seo from "$lib/components/Seo.svelte";
1011 import { invalidateAll } from "$app/navigation";
1112 import { endpoints } from "$lib/api";
1213 import type {
@@ -88,6 +89,8 @@
8889 ]);
8990 </script>
9091
92+<Seo title="Dashboard" />
93+
9194 <!-- Header -->
9295 <div
9396 class="mb-6 flex flex-wrap items-end justify-between gap-3 border-b-2 border-[var(--border)] pb-4"
@@ -7,6 +7,7 @@
7 import EmptyState from "$lib/components/EmptyState.svelte";7 import EmptyState from "$lib/components/EmptyState.svelte";
8 import Icon from "$lib/components/Icon.svelte";8 import Icon from "$lib/components/Icon.svelte";
9 import NewArticlesBanner from "$lib/components/NewArticlesBanner.svelte";9 import NewArticlesBanner from "$lib/components/NewArticlesBanner.svelte";
10+ import Seo from "$lib/components/Seo.svelte";
10 import { invalidateAll } from "$app/navigation";11 import { invalidateAll } from "$app/navigation";
11 import { endpoints } from "$lib/api";12 import { endpoints } from "$lib/api";
12 import type {13 import type {
@@ -88,6 +89,8 @@
88 ]);89 ]);
89 </script>90 </script>
90 91
92+<Seo title="Dashboard" />
93+
91 <!-- Header -->94 <!-- Header -->
92 <div95 <div
93 class="mb-6 flex flex-wrap items-end justify-between gap-3 border-b-2 border-[var(--border)] pb-4"96 class="mb-6 flex flex-wrap items-end justify-between gap-3 border-b-2 border-[var(--border)] pb-4"
modified web/src/routes/feeds/+page.svelte +3 -0
@@ -6,6 +6,7 @@
66 import EmptyState from "$lib/components/EmptyState.svelte";
77 import FeedRecommendationCard from "$lib/components/FeedRecommendationCard.svelte";
88 import Icon from "$lib/components/Icon.svelte";
9+ import Seo from "$lib/components/Seo.svelte";
910 import { endpoints } from "$lib/api";
1011 import { invalidateAll } from "$app/navigation";
1112 import type { FeedRecommendation } from "$lib/types";
@@ -90,6 +91,8 @@
9091 }
9192 </script>
9293
94+<Seo title="Discover feeds" />
95+
9396 <div class="flex items-center justify-between gap-3 mb-2 flex-wrap">
9497 <h1 class="text-2xl font-extrabold uppercase tracking-tight">
9598 Feeds <span class="text-base font-normal text-[var(--muted)]"
@@ -6,6 +6,7 @@
6 import EmptyState from "$lib/components/EmptyState.svelte";6 import EmptyState from "$lib/components/EmptyState.svelte";
7 import FeedRecommendationCard from "$lib/components/FeedRecommendationCard.svelte";7 import FeedRecommendationCard from "$lib/components/FeedRecommendationCard.svelte";
8 import Icon from "$lib/components/Icon.svelte";8 import Icon from "$lib/components/Icon.svelte";
9+ import Seo from "$lib/components/Seo.svelte";
9 import { endpoints } from "$lib/api";10 import { endpoints } from "$lib/api";
10 import { invalidateAll } from "$app/navigation";11 import { invalidateAll } from "$app/navigation";
11 import type { FeedRecommendation } from "$lib/types";12 import type { FeedRecommendation } from "$lib/types";
@@ -90,6 +91,8 @@
90 }91 }
91 </script>92 </script>
92 93
94+<Seo title="Discover feeds" />
95+
93 <div class="flex items-center justify-between gap-3 mb-2 flex-wrap">96 <div class="flex items-center justify-between gap-3 mb-2 flex-wrap">
94 <h1 class="text-2xl font-extrabold uppercase tracking-tight">97 <h1 class="text-2xl font-extrabold uppercase tracking-tight">
95 Feeds <span class="text-base font-normal text-[var(--muted)]"98 Feeds <span class="text-base font-normal text-[var(--muted)]"
modified web/src/routes/library/+page.svelte +3 -0
@@ -3,6 +3,7 @@
33 import ArticleCard from "$lib/components/ArticleCard.svelte";
44 import AnnotationCard from "$lib/components/AnnotationCard.svelte";
55 import EmptyState from "$lib/components/EmptyState.svelte";
6+ import Seo from "$lib/components/Seo.svelte";
67
78 let { data }: { data: PageData } = $props();
89
@@ -22,6 +23,8 @@
2223 }
2324 </script>
2425
26+<Seo title="Library" />
27+
2528 <h1 class="text-2xl font-extrabold uppercase tracking-tight mb-2">Library</h1>
2629 <p class="mb-6 text-xs uppercase tracking-widest text-[var(--muted)]">
2730 Your liked articles and annotations.
@@ -3,6 +3,7 @@
3 import ArticleCard from "$lib/components/ArticleCard.svelte";3 import ArticleCard from "$lib/components/ArticleCard.svelte";
4 import AnnotationCard from "$lib/components/AnnotationCard.svelte";4 import AnnotationCard from "$lib/components/AnnotationCard.svelte";
5 import EmptyState from "$lib/components/EmptyState.svelte";5 import EmptyState from "$lib/components/EmptyState.svelte";
6+ import Seo from "$lib/components/Seo.svelte";
6 7
7 let { data }: { data: PageData } = $props();8 let { data }: { data: PageData } = $props();
8 9
@@ -22,6 +23,8 @@
22 }23 }
23 </script>24 </script>
24 25
26+<Seo title="Library" />
27+
25 <h1 class="text-2xl font-extrabold uppercase tracking-tight mb-2">Library</h1>28 <h1 class="text-2xl font-extrabold uppercase tracking-tight mb-2">Library</h1>
26 <p class="mb-6 text-xs uppercase tracking-widest text-[var(--muted)]">29 <p class="mb-6 text-xs uppercase tracking-widest text-[var(--muted)]">
27 Your liked articles and annotations.30 Your liked articles and annotations.
modified web/src/routes/profile/[did]/+page.svelte +3 -0
@@ -6,6 +6,7 @@
66 import Icon from "$lib/components/Icon.svelte";
77 import BlueskyLogo from "$lib/components/BlueskyLogo.svelte";
88 import { endpoints } from "$lib/api";
9+ import Seo from "$lib/components/Seo.svelte";
910 import { invalidateAll } from "$app/navigation";
1011
1112 let { data }: { data: PageData } = $props();
@@ -39,6 +40,8 @@
3940 }
4041 </script>
4142
43+<Seo title={data.profile_user.display_name || data.profile_user.handle} />
44+
4245 <div class="mx-auto max-w-2xl">
4346 <section class="panel mb-6 p-4 sm:p-6">
4447 <div
@@ -6,6 +6,7 @@
6 import Icon from "$lib/components/Icon.svelte";6 import Icon from "$lib/components/Icon.svelte";
7 import BlueskyLogo from "$lib/components/BlueskyLogo.svelte";7 import BlueskyLogo from "$lib/components/BlueskyLogo.svelte";
8 import { endpoints } from "$lib/api";8 import { endpoints } from "$lib/api";
9+ import Seo from "$lib/components/Seo.svelte";
9 import { invalidateAll } from "$app/navigation";10 import { invalidateAll } from "$app/navigation";
10 11
11 let { data }: { data: PageData } = $props();12 let { data }: { data: PageData } = $props();
@@ -39,6 +40,8 @@
39 }40 }
40 </script>41 </script>
41 42
43+<Seo title={data.profile_user.display_name || data.profile_user.handle} />
44+
42 <div class="mx-auto max-w-2xl">45 <div class="mx-auto max-w-2xl">
43 <section class="panel mb-6 p-4 sm:p-6">46 <section class="panel mb-6 p-4 sm:p-6">
44 <div47 <div
added web/src/routes/robots.txt/+server.ts +19 -0
new file mode 100644
@@ -0,0 +1,19 @@
1+import type { RequestHandler } from "./$types";
2+
3+export const GET: RequestHandler = ({ url }) => {
4+ const body = [
5+ "User-agent: *",
6+ "Disallow: /dashboard",
7+ "Disallow: /articles",
8+ "Disallow: /feeds",
9+ "Disallow: /library",
10+ "Disallow: /profile",
11+ "Disallow: /stats",
12+ "",
13+ `Sitemap: ${url.origin}/sitemap.xml`,
14+ "",
15+ ].join("\n");
16+ return new Response(body, {
17+ headers: { "content-type": "text/plain; charset=utf-8" },
18+ });
19+};
new file mode 100644
@@ -0,0 +1,19 @@
1+import type { RequestHandler } from "./$types";
2+
3+export const GET: RequestHandler = ({ url }) => {
4+ const body = [
5+ "User-agent: *",
6+ "Disallow: /dashboard",
7+ "Disallow: /articles",
8+ "Disallow: /feeds",
9+ "Disallow: /library",
10+ "Disallow: /profile",
11+ "Disallow: /stats",
12+ "",
13+ `Sitemap: ${url.origin}/sitemap.xml`,
14+ "",
15+ ].join("\n");
16+ return new Response(body, {
17+ headers: { "content-type": "text/plain; charset=utf-8" },
18+ });
19+};
modified web/src/routes/stats/+page.svelte +3 -0
@@ -1,12 +1,15 @@
11 <script lang="ts">
22 import type { PageData } from "./$types";
33 import EmptyState from "$lib/components/EmptyState.svelte";
4+ import Seo from "$lib/components/Seo.svelte";
45
56 let { data }: { data: PageData } = $props();
67
78 const categories = $derived(Object.keys(data.metrics));
89 </script>
910
11+<Seo title="Stats" />
12+
1013 <h1 class="text-2xl font-extrabold uppercase tracking-tight mb-2">Stats</h1>
1114 <p class="mb-6 text-xs uppercase tracking-widest text-[var(--muted)]">
1215 Application metrics and performance data.
@@ -1,12 +1,15 @@
1 <script lang="ts">1 <script lang="ts">
2 import type { PageData } from "./$types";2 import type { PageData } from "./$types";
3 import EmptyState from "$lib/components/EmptyState.svelte";3 import EmptyState from "$lib/components/EmptyState.svelte";
4+ import Seo from "$lib/components/Seo.svelte";
4 5
5 let { data }: { data: PageData } = $props();6 let { data }: { data: PageData } = $props();
6 7
7 const categories = $derived(Object.keys(data.metrics));8 const categories = $derived(Object.keys(data.metrics));
8 </script>9 </script>
9 10
11+<Seo title="Stats" />
12+
10 <h1 class="text-2xl font-extrabold uppercase tracking-tight mb-2">Stats</h1>13 <h1 class="text-2xl font-extrabold uppercase tracking-tight mb-2">Stats</h1>
11 <p class="mb-6 text-xs uppercase tracking-widest text-[var(--muted)]">14 <p class="mb-6 text-xs uppercase tracking-widest text-[var(--muted)]">
12 Application metrics and performance data.15 Application metrics and performance data.
modified web/src/routes/terms/+page.svelte +9 -0
@@ -1,3 +1,12 @@
1+<script lang="ts">
2+ import Seo from "$lib/components/Seo.svelte";
3+</script>
4+
5+<Seo
6+ title="Terms of Service"
7+ description="Terms of service for Glean, the social RSS reader built on AT Protocol."
8+/>
9+
110 <div class="mx-auto max-w-2xl px-4 py-12">
211 <h1 class="text-2xl font-extrabold uppercase tracking-tight">
312 Terms of Service
@@ -1,3 +1,12 @@
1+<script lang="ts">
2+ import Seo from "$lib/components/Seo.svelte";
3+</script>
4+
5+<Seo
6+ title="Terms of Service"
7+ description="Terms of service for Glean, the social RSS reader built on AT Protocol."
8+/>
9+
1 <div class="mx-auto max-w-2xl px-4 py-12">10 <div class="mx-auto max-w-2xl px-4 py-12">
2 <h1 class="text-2xl font-extrabold uppercase tracking-tight">11 <h1 class="text-2xl font-extrabold uppercase tracking-tight">
3 Terms of Service12 Terms of Service