@aureuma/svelta 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +34 -2
- package/packages/blogkit/CHANGELOG.md +12 -0
- package/packages/blogkit/dist/index.d.ts +1 -1
- package/packages/blogkit/dist/server/blog.d.ts +68 -1
- package/packages/blogkit/dist/server/blog.js +248 -0
- package/packages/blogkit/dist/server/index.d.ts +1 -1
- package/packages/blogkit/dist/server/index.js +1 -1
- package/packages/blogkit/dist/types/blog.d.ts +10 -0
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -14
- package/.changeset/publish-blogkit.md +0 -5
- package/.github/workflows/release.yml +0 -65
- package/docs/mintlify-blog-study.md +0 -697
- package/packages/blogkit/package.json +0 -66
- package/packages/blogkit/src/lib/components/blog/Avatar.svelte +0 -15
- package/packages/blogkit/src/lib/components/blog/BackLink.svelte +0 -23
- package/packages/blogkit/src/lib/components/blog/BlogCard.svelte +0 -37
- package/packages/blogkit/src/lib/components/blog/BlogHeroCard.svelte +0 -36
- package/packages/blogkit/src/lib/components/blog/Container.svelte +0 -8
- package/packages/blogkit/src/lib/components/blog/ImageLightbox.svelte +0 -58
- package/packages/blogkit/src/lib/components/blog/MorePosts.svelte +0 -15
- package/packages/blogkit/src/lib/components/blog/ShareButtons.svelte +0 -113
- package/packages/blogkit/src/lib/components/blog/SummaryCard.svelte +0 -11
- package/packages/blogkit/src/lib/components/blog/TagTabs.svelte +0 -32
- package/packages/blogkit/src/lib/index.ts +0 -15
- package/packages/blogkit/src/lib/server/blog.ts +0 -264
- package/packages/blogkit/src/lib/server/index.ts +0 -2
- package/packages/blogkit/src/lib/theme/ThemeSwitcher.svelte +0 -34
- package/packages/blogkit/src/lib/theme/index.ts +0 -3
- package/packages/blogkit/src/lib/theme/store.ts +0 -64
- package/packages/blogkit/src/lib/types/blog.ts +0 -36
- package/packages/blogkit/svelte.config.js +0 -8
- package/packages/blogkit/tsconfig.json +0 -5
- package/playwright.config.ts +0 -24
- package/postcss.config.cjs +0 -6
- package/src/app.css +0 -146
- package/src/app.d.ts +0 -13
- package/src/app.html +0 -26
- package/src/content/blog/ai-summary-cards-with-frontmatter.md +0 -32
- package/src/content/blog/announcing-svelta-blog.md +0 -19
- package/src/content/blog/best-practices-ship-with-checklists.md +0 -26
- package/src/content/blog/building-a-mintlify-inspired-blog.md +0 -49
- package/src/content/blog/design-tokens-that-scale.md +0 -47
- package/src/content/blog/for-founders-why-speed-matters.md +0 -23
- package/src/content/blog/infinite-scroll-with-intersection-observer.md +0 -37
- package/src/content/blog/markdown-kitchen-sink.md +0 -101
- package/src/content/blog/markdown-pipeline-mdsvex-shiki.md +0 -39
- package/src/content/blog/rss-feeds-that-actually-work.md +0 -25
- package/src/content/blog/tag-tabs-and-mobile-fade-masks.md +0 -25
- package/src/lib/assets/favicon.svg +0 -1
- package/src/lib/components/site/SiteFooter.svelte +0 -24
- package/src/lib/components/site/SiteHeader.svelte +0 -36
- package/src/lib/content/authors.ts +0 -28
- package/src/lib/index.ts +0 -1
- package/src/lib/server/blog.ts +0 -22
- package/src/lib/server/rss.ts +0 -58
- package/src/lib/server/seo.ts +0 -31
- package/src/lib/stores/theme.ts +0 -10
- package/src/lib/types/blog.ts +0 -1
- package/src/routes/+layout.svelte +0 -31
- package/src/routes/+page.svelte +0 -44
- package/src/routes/blog/+page.server.ts +0 -28
- package/src/routes/blog/+page.svelte +0 -122
- package/src/routes/blog/[slug]/+page.server.ts +0 -39
- package/src/routes/blog/[slug]/+page.svelte +0 -118
- package/src/routes/blog/posts.json/+server.ts +0 -32
- package/src/routes/feed.xml/+server.ts +0 -21
- package/static/blog/authors/alex.svg +0 -13
- package/static/blog/authors/maria.svg +0 -13
- package/static/blog/authors/shawn.svg +0 -13
- package/static/blog/covers/ai-summary.svg +0 -38
- package/static/blog/covers/design-tokens.svg +0 -37
- package/static/blog/covers/infinite-scroll.svg +0 -38
- package/static/blog/covers/kitchen-sink.svg +0 -36
- package/static/blog/covers/markdown-pipeline.svg +0 -41
- package/static/blog/covers/mintlify-style.svg +0 -35
- package/static/blog/covers/rss.svg +0 -34
- package/static/robots.txt +0 -3
- package/svelte.config.js +0 -70
- package/tailwind.config.cjs +0 -133
- package/tests/blog.spec.ts +0 -63
- package/tsconfig.json +0 -21
- package/vite.config.ts +0 -14
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@aureuma/blogkit",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": false,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"files": [
|
|
7
|
-
"dist",
|
|
8
|
-
"README.md",
|
|
9
|
-
"CHANGELOG.md",
|
|
10
|
-
"LICENSE"
|
|
11
|
-
],
|
|
12
|
-
"exports": {
|
|
13
|
-
".": {
|
|
14
|
-
"types": "./dist/index.d.ts",
|
|
15
|
-
"svelte": "./dist/index.js",
|
|
16
|
-
"default": "./dist/index.js"
|
|
17
|
-
},
|
|
18
|
-
"./server": {
|
|
19
|
-
"types": "./dist/server/index.d.ts",
|
|
20
|
-
"default": "./dist/server/index.js"
|
|
21
|
-
},
|
|
22
|
-
"./theme": {
|
|
23
|
-
"types": "./dist/theme/index.d.ts",
|
|
24
|
-
"svelte": "./dist/theme/index.js",
|
|
25
|
-
"default": "./dist/theme/index.js"
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
"types": "./dist/index.d.ts",
|
|
29
|
-
"main": "./dist/index.js",
|
|
30
|
-
"svelte": "./dist/index.js",
|
|
31
|
-
"license": "MIT",
|
|
32
|
-
"repository": {
|
|
33
|
-
"type": "git",
|
|
34
|
-
"url": "git+ssh://git@github.com/Aureuma/svelta.git",
|
|
35
|
-
"directory": "packages/blogkit"
|
|
36
|
-
},
|
|
37
|
-
"bugs": {
|
|
38
|
-
"url": "https://github.com/Aureuma/svelta/issues"
|
|
39
|
-
},
|
|
40
|
-
"homepage": "https://github.com/Aureuma/svelta/tree/main/packages/blogkit#readme",
|
|
41
|
-
"keywords": [
|
|
42
|
-
"svelte",
|
|
43
|
-
"sveltekit",
|
|
44
|
-
"blog",
|
|
45
|
-
"markdown",
|
|
46
|
-
"mdsvex",
|
|
47
|
-
"shiki",
|
|
48
|
-
"tailwind"
|
|
49
|
-
],
|
|
50
|
-
"publishConfig": {
|
|
51
|
-
"access": "public"
|
|
52
|
-
},
|
|
53
|
-
"scripts": {
|
|
54
|
-
"build": "svelte-package --tsconfig ./tsconfig.json",
|
|
55
|
-
"prepack": "npm run build"
|
|
56
|
-
},
|
|
57
|
-
"peerDependencies": {
|
|
58
|
-
"svelte": "^4.0.0 || ^5.0.0"
|
|
59
|
-
},
|
|
60
|
-
"dependencies": {
|
|
61
|
-
"esm-env": "^1.2.2",
|
|
62
|
-
"gray-matter": "^4.0.3",
|
|
63
|
-
"reading-time": "^1.5.0",
|
|
64
|
-
"zod": "^4.3.6"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
export let src: string;
|
|
3
|
-
export let alt: string;
|
|
4
|
-
export let size: number = 48;
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<img
|
|
8
|
-
src={src}
|
|
9
|
-
alt={alt}
|
|
10
|
-
width={size}
|
|
11
|
-
height={size}
|
|
12
|
-
class="shrink-0 rounded-full border border-border-soft/10 bg-background-soft object-cover"
|
|
13
|
-
style="width: {size}px; height: {size}px;"
|
|
14
|
-
loading="lazy"
|
|
15
|
-
/>
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
export let href: string = '/blog';
|
|
3
|
-
export let label: string = 'Back to blog';
|
|
4
|
-
</script>
|
|
5
|
-
|
|
6
|
-
<a
|
|
7
|
-
href={href}
|
|
8
|
-
class="inline-flex items-center gap-2 text-xs font-mono uppercase tracking-[0.6px] text-text-sub transition hover:text-text-main"
|
|
9
|
-
>
|
|
10
|
-
<svg
|
|
11
|
-
class="size-4"
|
|
12
|
-
viewBox="0 0 24 24"
|
|
13
|
-
fill="none"
|
|
14
|
-
stroke="currentColor"
|
|
15
|
-
stroke-width="2"
|
|
16
|
-
stroke-linecap="round"
|
|
17
|
-
stroke-linejoin="round"
|
|
18
|
-
aria-hidden="true"
|
|
19
|
-
>
|
|
20
|
-
<path d="M15 18l-6-6 6-6" />
|
|
21
|
-
</svg>
|
|
22
|
-
<span>{label}</span>
|
|
23
|
-
</a>
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { BlogPost } from '../../types/blog';
|
|
3
|
-
import Avatar from './Avatar.svelte';
|
|
4
|
-
|
|
5
|
-
export let post: BlogPost;
|
|
6
|
-
export let variant: 'default' | 'suggestion' = 'default';
|
|
7
|
-
$: thumbHeight = variant === 'suggestion' ? 'h-[190px]' : 'h-[280px]';
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
|
-
<a href={`/blog/${post.slug}`} class="group block" data-testid="blog-card">
|
|
11
|
-
<div class="relative overflow-hidden rounded-2xl border border-border-soft/10 bg-background-soft {thumbHeight}">
|
|
12
|
-
<img
|
|
13
|
-
src={post.cover}
|
|
14
|
-
alt={post.title}
|
|
15
|
-
class="h-full w-full object-cover transition-transform duration-500 group-hover:scale-[1.02]"
|
|
16
|
-
loading="lazy"
|
|
17
|
-
/>
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
|
-
<div class="mt-4">
|
|
21
|
-
<p class="text-xs font-mono uppercase tracking-[0.6px] text-brand">{post.category.label}</p>
|
|
22
|
-
<h3
|
|
23
|
-
class="mt-1 text-xl font-medium leading-[30px] tracking-tight underline-offset-[6px] decoration-border-soft/30 group-hover:underline"
|
|
24
|
-
>
|
|
25
|
-
{post.title}
|
|
26
|
-
</h3>
|
|
27
|
-
<p class="mt-2 text-sm leading-6 text-text-sub">{post.excerpt}</p>
|
|
28
|
-
|
|
29
|
-
<div class="mt-4 flex items-center gap-3">
|
|
30
|
-
<Avatar src={post.author.avatar} alt={post.author.name} size={24} />
|
|
31
|
-
<div class="leading-tight">
|
|
32
|
-
<div class="text-sm font-medium tracking-tight text-text-main">{post.author.name}</div>
|
|
33
|
-
<div class="text-xs text-text-muted">{post.author.title}</div>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</a>
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { BlogPost } from '../../types/blog';
|
|
3
|
-
|
|
4
|
-
export let post: BlogPost;
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<a
|
|
8
|
-
href={`/blog/${post.slug}`}
|
|
9
|
-
data-testid="blog-hero"
|
|
10
|
-
class="group relative my-12 block h-[480px] overflow-hidden rounded-3xl shadow-drop-md"
|
|
11
|
-
>
|
|
12
|
-
<img
|
|
13
|
-
src={post.cover}
|
|
14
|
-
alt={post.title}
|
|
15
|
-
class="absolute inset-0 h-full w-full object-cover transition-transform duration-700 group-hover:scale-[1.02]"
|
|
16
|
-
loading="eager"
|
|
17
|
-
/>
|
|
18
|
-
|
|
19
|
-
<div class="absolute inset-0 bg-gradient-to-t from-black/65 via-black/25 to-black/0"></div>
|
|
20
|
-
|
|
21
|
-
<div class="relative flex h-full flex-col justify-end p-8">
|
|
22
|
-
<p class="text-xs font-mono uppercase tracking-[0.6px] text-brand">{post.category.label}</p>
|
|
23
|
-
<h2
|
|
24
|
-
class="mt-2 max-w-3xl text-3xl font-semibold leading-tight tracking-[-0.8px] text-white md:text-4xl"
|
|
25
|
-
>
|
|
26
|
-
{post.title}
|
|
27
|
-
</h2>
|
|
28
|
-
<p class="mt-3 max-w-2xl text-base leading-6 text-white/80">{post.excerpt}</p>
|
|
29
|
-
|
|
30
|
-
<div class="mt-6 flex items-center gap-2 text-xs font-mono uppercase tracking-[0.6px] text-white/70">
|
|
31
|
-
<span>{post.dateShort}</span>
|
|
32
|
-
<span class="text-white/35" aria-hidden="true">•</span>
|
|
33
|
-
<span>{post.readingTimeShort}</span>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</a>
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { BROWSER } from 'esm-env';
|
|
3
|
-
import { onDestroy, onMount, tick } from 'svelte';
|
|
4
|
-
|
|
5
|
-
export let image: { src: string; alt: string } | null;
|
|
6
|
-
export let onClose: () => void;
|
|
7
|
-
|
|
8
|
-
let dialogEl: HTMLDivElement | null = null;
|
|
9
|
-
|
|
10
|
-
$: if (BROWSER) {
|
|
11
|
-
// Prevent background scroll while open.
|
|
12
|
-
document.body.style.overflow = image ? 'hidden' : '';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
$: if (BROWSER && image) {
|
|
16
|
-
// Ensure the dialog is focusable for Escape-to-close and accessibility.
|
|
17
|
-
void (async () => {
|
|
18
|
-
await tick();
|
|
19
|
-
dialogEl?.focus();
|
|
20
|
-
})();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
onMount(() => {
|
|
24
|
-
if (!BROWSER) return;
|
|
25
|
-
const onKeyDown = (e: KeyboardEvent) => {
|
|
26
|
-
if (e.key === 'Escape') onClose();
|
|
27
|
-
};
|
|
28
|
-
window.addEventListener('keydown', onKeyDown);
|
|
29
|
-
return () => window.removeEventListener('keydown', onKeyDown);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
onDestroy(() => {
|
|
33
|
-
if (!BROWSER) return;
|
|
34
|
-
document.body.style.overflow = '';
|
|
35
|
-
});
|
|
36
|
-
</script>
|
|
37
|
-
|
|
38
|
-
{#if image}
|
|
39
|
-
<div
|
|
40
|
-
bind:this={dialogEl}
|
|
41
|
-
class="fixed inset-0 z-50 flex items-center justify-center bg-black/75 p-6"
|
|
42
|
-
role="dialog"
|
|
43
|
-
aria-modal="true"
|
|
44
|
-
tabindex="-1"
|
|
45
|
-
>
|
|
46
|
-
<button
|
|
47
|
-
type="button"
|
|
48
|
-
class="absolute inset-0 cursor-zoom-out"
|
|
49
|
-
on:click={onClose}
|
|
50
|
-
aria-label="Close image"
|
|
51
|
-
></button>
|
|
52
|
-
<img
|
|
53
|
-
src={image.src}
|
|
54
|
-
alt={image.alt}
|
|
55
|
-
class="relative max-h-[90vh] max-w-[92vw] rounded-2xl border border-white/10 bg-black/10 object-contain"
|
|
56
|
-
/>
|
|
57
|
-
</div>
|
|
58
|
-
{/if}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { BlogPost } from '../../types/blog';
|
|
3
|
-
import BlogCard from './BlogCard.svelte';
|
|
4
|
-
|
|
5
|
-
export let posts: BlogPost[];
|
|
6
|
-
</script>
|
|
7
|
-
|
|
8
|
-
<section class="mt-16" data-testid="blog-more-posts">
|
|
9
|
-
<h2 class="text-lg font-medium tracking-tight text-text-main">More blog posts to read</h2>
|
|
10
|
-
<div class="mt-8 grid grid-cols-1 gap-x-5 gap-y-12 md:grid-cols-2">
|
|
11
|
-
{#each posts as post (post.slug)}
|
|
12
|
-
<BlogCard post={post} variant="suggestion" />
|
|
13
|
-
{/each}
|
|
14
|
-
</div>
|
|
15
|
-
</section>
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { BROWSER } from 'esm-env';
|
|
3
|
-
|
|
4
|
-
export let title: string;
|
|
5
|
-
export let url: string;
|
|
6
|
-
export let label: string = 'Share this article';
|
|
7
|
-
export let testId: string | undefined = undefined;
|
|
8
|
-
|
|
9
|
-
let copied = false;
|
|
10
|
-
|
|
11
|
-
function openShare(href: string) {
|
|
12
|
-
if (!BROWSER) return;
|
|
13
|
-
window.open(href, '_blank', 'noopener,noreferrer');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function copyLink() {
|
|
17
|
-
if (!BROWSER) return;
|
|
18
|
-
try {
|
|
19
|
-
await navigator.clipboard.writeText(url);
|
|
20
|
-
copied = true;
|
|
21
|
-
window.setTimeout(() => (copied = false), 1200);
|
|
22
|
-
} catch {
|
|
23
|
-
// Fallback for older browsers.
|
|
24
|
-
const ta = document.createElement('textarea');
|
|
25
|
-
ta.value = url;
|
|
26
|
-
ta.style.position = 'fixed';
|
|
27
|
-
ta.style.opacity = '0';
|
|
28
|
-
document.body.appendChild(ta);
|
|
29
|
-
ta.select();
|
|
30
|
-
document.execCommand('copy');
|
|
31
|
-
document.body.removeChild(ta);
|
|
32
|
-
copied = true;
|
|
33
|
-
window.setTimeout(() => (copied = false), 1200);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
$: encodedUrl = encodeURIComponent(url);
|
|
38
|
-
$: encodedTitle = encodeURIComponent(title);
|
|
39
|
-
</script>
|
|
40
|
-
|
|
41
|
-
<div data-testid={testId}>
|
|
42
|
-
<p class="text-xs font-mono uppercase tracking-[0.6px] text-text-muted">{label}</p>
|
|
43
|
-
<div class="mt-3 flex flex-wrap gap-2">
|
|
44
|
-
<button
|
|
45
|
-
type="button"
|
|
46
|
-
class="inline-flex size-8 items-center justify-center rounded-full border border-border-soft/10 bg-background-soft text-text-sub transition hover:bg-background-main/60 hover:text-text-main"
|
|
47
|
-
on:click={() =>
|
|
48
|
-
openShare(`https://twitter.com/intent/tweet?text=${encodedTitle}&url=${encodedUrl}`)}
|
|
49
|
-
aria-label="Share on X"
|
|
50
|
-
>
|
|
51
|
-
<svg viewBox="0 0 24 24" class="size-4" aria-hidden="true">
|
|
52
|
-
<path
|
|
53
|
-
fill="currentColor"
|
|
54
|
-
d="M18.9 2H22l-6.8 7.8L23 22h-6.8l-5.3-6.9L4.9 22H2l7.4-8.6L1.5 2h7l4.8 6.2L18.9 2Zm-1.2 18h1.7L7.8 3.9H6.1L17.7 20Z"
|
|
55
|
-
/>
|
|
56
|
-
</svg>
|
|
57
|
-
</button>
|
|
58
|
-
|
|
59
|
-
<button
|
|
60
|
-
type="button"
|
|
61
|
-
class="inline-flex size-8 items-center justify-center rounded-full border border-border-soft/10 bg-background-soft text-text-sub transition hover:bg-background-main/60 hover:text-text-main"
|
|
62
|
-
on:click={() =>
|
|
63
|
-
openShare(`https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`)}
|
|
64
|
-
aria-label="Share on LinkedIn"
|
|
65
|
-
>
|
|
66
|
-
<svg viewBox="0 0 24 24" class="size-4" aria-hidden="true">
|
|
67
|
-
<path
|
|
68
|
-
fill="currentColor"
|
|
69
|
-
d="M4.98 3.5C4.98 4.88 3.87 6 2.5 6S0 4.88 0 3.5 1.12 1 2.5 1s2.48 1.12 2.48 2.5ZM0.5 23.5h4V7.98h-4V23.5ZM8 7.98h3.84v2.12h.05c.53-1 1.83-2.12 3.77-2.12 4.03 0 4.78 2.65 4.78 6.1v9.42h-4v-8.36c0-1.99-.03-4.55-2.77-4.55-2.77 0-3.2 2.16-3.2 4.4v8.5H8V7.98Z"
|
|
70
|
-
/>
|
|
71
|
-
</svg>
|
|
72
|
-
</button>
|
|
73
|
-
|
|
74
|
-
<button
|
|
75
|
-
type="button"
|
|
76
|
-
class="inline-flex size-8 items-center justify-center rounded-full border border-border-soft/10 bg-background-soft text-text-sub transition hover:bg-background-main/60 hover:text-text-main"
|
|
77
|
-
on:click={() => openShare(`https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`)}
|
|
78
|
-
aria-label="Share on Facebook"
|
|
79
|
-
>
|
|
80
|
-
<svg viewBox="0 0 24 24" class="size-4" aria-hidden="true">
|
|
81
|
-
<path
|
|
82
|
-
fill="currentColor"
|
|
83
|
-
d="M13.5 22v-8h2.7l.4-3H13.5V9.1c0-.9.3-1.6 1.7-1.6h1.6V4.8c-.3 0-1.4-.1-2.7-.1-2.7 0-4.5 1.6-4.5 4.6V11H7v3h2.6v8h3.9Z"
|
|
84
|
-
/>
|
|
85
|
-
</svg>
|
|
86
|
-
</button>
|
|
87
|
-
|
|
88
|
-
<button
|
|
89
|
-
type="button"
|
|
90
|
-
class="inline-flex size-8 items-center justify-center rounded-full border border-border-soft/10 bg-background-soft text-text-sub transition hover:bg-background-main/60 hover:text-text-main"
|
|
91
|
-
on:click={copyLink}
|
|
92
|
-
aria-label="Copy link"
|
|
93
|
-
>
|
|
94
|
-
<svg
|
|
95
|
-
viewBox="0 0 24 24"
|
|
96
|
-
class="size-4"
|
|
97
|
-
fill="none"
|
|
98
|
-
stroke="currentColor"
|
|
99
|
-
stroke-width="2"
|
|
100
|
-
stroke-linecap="round"
|
|
101
|
-
stroke-linejoin="round"
|
|
102
|
-
aria-hidden="true"
|
|
103
|
-
>
|
|
104
|
-
<path d="M10 13a5 5 0 0 1 0-7l1-1a5 5 0 0 1 7 7l-1 1" />
|
|
105
|
-
<path d="M14 11a5 5 0 0 1 0 7l-1 1a5 5 0 0 1-7-7l1-1" />
|
|
106
|
-
</svg>
|
|
107
|
-
</button>
|
|
108
|
-
</div>
|
|
109
|
-
|
|
110
|
-
{#if copied}
|
|
111
|
-
<p class="mt-2 text-xs font-mono uppercase tracking-[0.6px] text-brand">Copied</p>
|
|
112
|
-
{/if}
|
|
113
|
-
</div>
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
export let summary: string;
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
|
-
<section
|
|
6
|
-
class="rounded-2xl border border-border-soft/10 bg-background-soft p-4"
|
|
7
|
-
data-testid="blog-summary"
|
|
8
|
-
>
|
|
9
|
-
<p class="text-xs font-mono uppercase tracking-[0.6px] text-text-muted">AI SUMMARY</p>
|
|
10
|
-
<p class="mt-2 text-sm leading-6 text-text-sub">{summary}</p>
|
|
11
|
-
</section>
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { BlogCategory } from '../../types/blog';
|
|
3
|
-
|
|
4
|
-
export let categories: BlogCategory[];
|
|
5
|
-
export let selected: string; // "" means all
|
|
6
|
-
export let onSelect: (slug: string) => void;
|
|
7
|
-
$: items = [{ label: 'All articles', slug: '' }, ...categories];
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
|
-
<div class="relative" data-testid="blog-tags">
|
|
11
|
-
<div
|
|
12
|
-
class="fade-mask-x no-scrollbar flex gap-2 overflow-x-auto py-2 md:flex-wrap md:overflow-visible md:[-webkit-mask-image:none] md:[mask-image:none]"
|
|
13
|
-
role="tablist"
|
|
14
|
-
aria-label="Blog categories"
|
|
15
|
-
>
|
|
16
|
-
{#each items as item (item.slug)}
|
|
17
|
-
<button
|
|
18
|
-
type="button"
|
|
19
|
-
role="tab"
|
|
20
|
-
aria-selected={selected === item.slug}
|
|
21
|
-
class="h-[31px] whitespace-nowrap rounded-full border px-3 text-xs font-mono uppercase tracking-[0.6px] transition
|
|
22
|
-
hover:bg-background-main/60
|
|
23
|
-
{selected === item.slug
|
|
24
|
-
? 'border-border-soft/15 bg-background-main text-text-main'
|
|
25
|
-
: 'border-border-soft/10 bg-background-soft text-text-sub'}"
|
|
26
|
-
on:click={() => onSelect(item.slug)}
|
|
27
|
-
>
|
|
28
|
-
{item.label}
|
|
29
|
-
</button>
|
|
30
|
-
{/each}
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// Components
|
|
2
|
-
export { default as Avatar } from './components/blog/Avatar.svelte';
|
|
3
|
-
export { default as BackLink } from './components/blog/BackLink.svelte';
|
|
4
|
-
export { default as BlogCard } from './components/blog/BlogCard.svelte';
|
|
5
|
-
export { default as BlogHeroCard } from './components/blog/BlogHeroCard.svelte';
|
|
6
|
-
export { default as Container } from './components/blog/Container.svelte';
|
|
7
|
-
export { default as ImageLightbox } from './components/blog/ImageLightbox.svelte';
|
|
8
|
-
export { default as MorePosts } from './components/blog/MorePosts.svelte';
|
|
9
|
-
export { default as ShareButtons } from './components/blog/ShareButtons.svelte';
|
|
10
|
-
export { default as SummaryCard } from './components/blog/SummaryCard.svelte';
|
|
11
|
-
export { default as TagTabs } from './components/blog/TagTabs.svelte';
|
|
12
|
-
|
|
13
|
-
// Types
|
|
14
|
-
export type { BlogAuthor, BlogCategory, BlogPost, BlogPostFull } from './types/blog';
|
|
15
|
-
|