-
+
{post.tags &&
post.tags.length > 0 &&
From 4c4f4b8f4a7ad9b52e0afb6e2c3a2796b7862dc0 Mon Sep 17 00:00:00 2001
From: olliethedev <5933733+olliethedev@users.noreply.github.com>
Date: Sat, 12 Sep 2026 12:40:37 -0400
Subject: [PATCH 2/2] chore: update shadcn registry [skip ci]
---
packages/stack/registry/btst-blog.json | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/packages/stack/registry/btst-blog.json b/packages/stack/registry/btst-blog.json
index 4404e1d08..1454e2a34 100644
--- a/packages/stack/registry/btst-blog.json
+++ b/packages/stack/registry/btst-blog.json
@@ -196,7 +196,7 @@
{
"path": "btst/blog/client/components/pages/post-page.internal.tsx",
"type": "registry:component",
- "content": "\"use client\";\n\nimport {\n\tusePluginOverrides,\n\tuseStack,\n\tuseTranslate,\n} from \"@btst/stack/context\";\nimport { formatDate } from \"date-fns\";\nimport {\n\tuseSuspensePost,\n\tuseNextPreviousPosts,\n\tuseRecentPosts,\n} from \"@btst/stack/plugins/blog/client/hooks\";\nimport { EmptyList } from \"../shared/empty-list\";\nimport { MarkdownContent } from \"../shared/markdown-content\";\nimport { PageHeader } from \"../shared/page-header\";\nimport { PageWrapper } from \"../shared/page-wrapper\";\nimport type { BlogPluginOverrides } from \"../../overrides\";\nimport { BLOG_PLUGIN_ID } from \"../../constants\";\nimport { DefaultImage } from \"../shared/defaults\";\nimport { PostNavigation } from \"../shared/post-navigation\";\nimport { RecentPostsCarousel } from \"../shared/recent-posts-carousel\";\nimport { useRouteLifecycle } from \"@/hooks/use-route-lifecycle\";\nimport { OnThisPage, OnThisPageSelect } from \"../shared/on-this-page\";\nimport type { SerializedPost } from \"../../../types\";\nimport { useRegisterPageAIContext } from \"@btst/stack/plugins/ai-chat/client/context\";\nimport { WhenVisible } from \"@/components/ui/when-visible\";\nimport { PostNavigationSkeleton } from \"../loading/post-navigation-skeleton\";\nimport { RecentPostsCarouselSkeleton } from \"../loading/recent-posts-carousel-skeleton\";\nimport { CollapsibleTagList } from \"../shared/collapsible-tag-list\";\n\n// Internal component with actual page content\nexport function PostPage({ slug }: { slug: string }) {\n\tconst t = useTranslate();\n\tconst overrides = usePluginOverrides
(BLOG_PLUGIN_ID);\n\tconst { localization } = overrides;\n\tconst { router } = useStack();\n\tconst Image = router?.Image ?? DefaultImage;\n\n\t// Call lifecycle hooks\n\tuseRouteLifecycle({\n\t\trouteName: \"post\",\n\t\tcontext: {\n\t\t\tpath: `/blog/${slug}`,\n\t\t\tparams: { slug },\n\t\t\tisSSR: typeof window === \"undefined\",\n\t\t},\n\t\toverrides,\n\t});\n\n\tconst { post } = useSuspensePost(slug ?? \"\");\n\n\tconst { previousPost, nextPost } = useNextPreviousPosts(\n\t\tpost?.createdAt ?? new Date(),\n\t\t{\n\t\t\tenabled: !!post,\n\t\t},\n\t);\n\n\tconst { recentPosts } = useRecentPosts({\n\t\tlimit: 5,\n\t\texcludeSlug: slug,\n\t\tenabled: !!post,\n\t});\n\n\t// Register page AI context so the chat can summarize and discuss this post\n\tuseRegisterPageAIContext(\n\t\tpost\n\t\t\t? {\n\t\t\t\t\trouteName: \"blog-post\",\n\t\t\t\t\tpageDescription:\n\t\t\t\t\t\t`Blog post: \"${post.title}\"\\nAuthor: ${post.authorId ?? \"Unknown\"}\\n\\n${post.content ?? \"\"}`.slice(\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t16000,\n\t\t\t\t\t\t),\n\t\t\t\t\tsuggestions: [\n\t\t\t\t\t\t\"Summarize this post\",\n\t\t\t\t\t\t\"What are the key takeaways?\",\n\t\t\t\t\t\t\"Explain this in simpler terms\",\n\t\t\t\t\t],\n\t\t\t\t}\n\t\t\t: null,\n\t);\n\n\tif (!slug || !post) {\n\t\treturn (\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t
}\n\t\t\t\t\t/>\n\n\t\t\t\t\t{post.image && (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t)}\n\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\n\t\t\t\t\t
\n\t\t\t\t\t\t
}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\n\n\t\t\t\t\t\t
}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\n\n\t\t\t\t\t\t{overrides.postBottomSlot && (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t{overrides.postBottomSlot(post)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t\n\t);\n}\n\nfunction PostHeaderTop({ post }: { post: SerializedPost }) {\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t{formatDate(post.createdAt, \"MMMM d, yyyy\")}\n\t\t\t\n\t\t\t\n\t\t
\n\t);\n}\n",
+ "content": "\"use client\";\n\nimport {\n\tusePluginOverrides,\n\tuseStack,\n\tuseTranslate,\n} from \"@btst/stack/context\";\nimport { formatPostDate } from \"../shared/format-post-date\";\nimport {\n\tuseSuspensePost,\n\tuseNextPreviousPosts,\n\tuseRecentPosts,\n} from \"@btst/stack/plugins/blog/client/hooks\";\nimport { EmptyList } from \"../shared/empty-list\";\nimport { MarkdownContent } from \"../shared/markdown-content\";\nimport { PageHeader } from \"../shared/page-header\";\nimport { PageWrapper } from \"../shared/page-wrapper\";\nimport type { BlogPluginOverrides } from \"../../overrides\";\nimport { BLOG_PLUGIN_ID } from \"../../constants\";\nimport { DefaultImage } from \"../shared/defaults\";\nimport { PostNavigation } from \"../shared/post-navigation\";\nimport { RecentPostsCarousel } from \"../shared/recent-posts-carousel\";\nimport { useRouteLifecycle } from \"@/hooks/use-route-lifecycle\";\nimport { OnThisPage, OnThisPageSelect } from \"../shared/on-this-page\";\nimport type { SerializedPost } from \"../../../types\";\nimport { useRegisterPageAIContext } from \"@btst/stack/plugins/ai-chat/client/context\";\nimport { WhenVisible } from \"@/components/ui/when-visible\";\nimport { PostNavigationSkeleton } from \"../loading/post-navigation-skeleton\";\nimport { RecentPostsCarouselSkeleton } from \"../loading/recent-posts-carousel-skeleton\";\nimport { CollapsibleTagList } from \"../shared/collapsible-tag-list\";\n\n// Internal component with actual page content\nexport function PostPage({ slug }: { slug: string }) {\n\tconst t = useTranslate();\n\tconst overrides = usePluginOverrides(BLOG_PLUGIN_ID);\n\tconst { localization } = overrides;\n\tconst { router } = useStack();\n\tconst Image = router?.Image ?? DefaultImage;\n\n\t// Call lifecycle hooks\n\tuseRouteLifecycle({\n\t\trouteName: \"post\",\n\t\tcontext: {\n\t\t\tpath: `/blog/${slug}`,\n\t\t\tparams: { slug },\n\t\t\tisSSR: typeof window === \"undefined\",\n\t\t},\n\t\toverrides,\n\t});\n\n\tconst { post } = useSuspensePost(slug ?? \"\");\n\n\tconst { previousPost, nextPost } = useNextPreviousPosts(\n\t\tpost?.createdAt ?? new Date(),\n\t\t{\n\t\t\tenabled: !!post,\n\t\t},\n\t);\n\n\tconst { recentPosts } = useRecentPosts({\n\t\tlimit: 5,\n\t\texcludeSlug: slug,\n\t\tenabled: !!post,\n\t});\n\n\t// Register page AI context so the chat can summarize and discuss this post\n\tuseRegisterPageAIContext(\n\t\tpost\n\t\t\t? {\n\t\t\t\t\trouteName: \"blog-post\",\n\t\t\t\t\tpageDescription:\n\t\t\t\t\t\t`Blog post: \"${post.title}\"\\nAuthor: ${post.authorId ?? \"Unknown\"}\\n\\n${post.content ?? \"\"}`.slice(\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t16000,\n\t\t\t\t\t\t),\n\t\t\t\t\tsuggestions: [\n\t\t\t\t\t\t\"Summarize this post\",\n\t\t\t\t\t\t\"What are the key takeaways?\",\n\t\t\t\t\t\t\"Explain this in simpler terms\",\n\t\t\t\t\t],\n\t\t\t\t}\n\t\t\t: null,\n\t);\n\n\tif (!slug || !post) {\n\t\treturn (\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t
}\n\t\t\t\t\t/>\n\n\t\t\t\t\t{post.image && (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t)}\n\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\n\t\t\t\t\t
\n\t\t\t\t\t\t
}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\n\n\t\t\t\t\t\t
}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\n\n\t\t\t\t\t\t{overrides.postBottomSlot && (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t{overrides.postBottomSlot(post)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t\n\t);\n}\n\nfunction PostHeaderTop({ post }: { post: SerializedPost }) {\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t{formatPostDate(post.createdAt)}\n\t\t\t\n\t\t\t\n\t\t
\n\t);\n}\n",
"target": "src/components/btst/blog/client/components/pages/post-page.internal.tsx"
},
{
@@ -247,6 +247,12 @@
"content": "import { CloudAlert } from \"lucide-react\";\nimport { PageHeader } from \"./page-header\";\n\nexport function ErrorPlaceholder({\n\ttitle,\n\tmessage,\n}: {\n\ttitle: string;\n\tmessage: string;\n}) {\n\treturn (\n\t\t\n\t);\n}\n",
"target": "src/components/btst/blog/client/components/shared/error-placeholder.tsx"
},
+ {
+ "path": "btst/blog/client/components/shared/format-post-date.ts",
+ "type": "registry:lib",
+ "content": "// Public article dates must render identically during SSR and browser hydration.\n// Keep the existing English date style, with an explicit publication timezone.\nconst formatter = new Intl.DateTimeFormat(\"en-US\", {\n\tmonth: \"long\",\n\tday: \"numeric\",\n\tyear: \"numeric\",\n\ttimeZone: \"UTC\",\n});\n\nexport function formatPostDate(date: string) {\n\treturn formatter.format(new Date(date));\n}\n",
+ "target": "src/components/btst/blog/client/components/shared/format-post-date.ts"
+ },
{
"path": "btst/blog/client/components/shared/highlight-text.tsx",
"type": "registry:component",
@@ -286,7 +292,7 @@
{
"path": "btst/blog/client/components/shared/post-card.tsx",
"type": "registry:component",
- "content": "\"use client\";\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n\tCard,\n\tCardContent,\n\tCardHeader,\n\tCardTitle,\n} from \"@/components/ui/card\";\nimport {\n\tuseBasePath,\n\tusePluginOverrides,\n\tuseStack,\n\tuseTranslate,\n} from \"@btst/stack/context\";\nimport { formatDate } from \"date-fns\";\nimport type { SerializedPost } from \"../../../types\";\nimport { CalendarIcon } from \"lucide-react\";\nimport type { BlogPluginOverrides } from \"../../overrides\";\nimport { BLOG_PLUGIN_ID } from \"../../constants\";\nimport { DefaultLink, DefaultImage } from \"./defaults\";\n\n// Beautiful gradient color combinations\nconst GRADIENT_PALETTES = [\n\t{\n\t\tfrom: \"from-purple-500\",\n\t\tvia: \"via-pink-500\",\n\t\tto: \"to-orange-500\",\n\t\toverlay: \"from-blue-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-blue-500\",\n\t\tvia: \"via-cyan-500\",\n\t\tto: \"to-teal-500\",\n\t\toverlay: \"from-indigo-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-pink-500\",\n\t\tvia: \"via-rose-500\",\n\t\tto: \"to-orange-500\",\n\t\toverlay: \"from-purple-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-green-500\",\n\t\tvia: \"via-emerald-500\",\n\t\tto: \"to-teal-500\",\n\t\toverlay: \"from-cyan-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-indigo-500\",\n\t\tvia: \"via-purple-500\",\n\t\tto: \"to-pink-500\",\n\t\toverlay: \"from-violet-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-orange-500\",\n\t\tvia: \"via-amber-500\",\n\t\tto: \"to-yellow-500\",\n\t\toverlay: \"from-orange-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-cyan-500\",\n\t\tvia: \"via-blue-500\",\n\t\tto: \"to-indigo-500\",\n\t\toverlay: \"from-purple-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-violet-500\",\n\t\tvia: \"via-purple-500\",\n\t\tto: \"to-fuchsia-500\",\n\t\toverlay: \"from-pink-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-emerald-500\",\n\t\tvia: \"via-green-500\",\n\t\tto: \"to-lime-500\",\n\t\toverlay: \"from-teal-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-rose-500\",\n\t\tvia: \"via-pink-500\",\n\t\tto: \"to-fuchsia-500\",\n\t\toverlay: \"from-purple-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-sky-500\",\n\t\tvia: \"via-blue-500\",\n\t\tto: \"to-indigo-600\",\n\t\toverlay: \"from-purple-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-fuchsia-600\",\n\t\tvia: \"via-purple-600\",\n\t\tto: \"to-indigo-600\",\n\t\toverlay: \"from-pink-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-teal-500\",\n\t\tvia: \"via-emerald-500\",\n\t\tto: \"to-green-600\",\n\t\toverlay: \"from-cyan-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-amber-500\",\n\t\tvia: \"via-orange-500\",\n\t\tto: \"to-pink-500\",\n\t\toverlay: \"from-yellow-600/40\",\n\t},\n\t{\n\t\tfrom: \"from-indigo-600\",\n\t\tvia: \"via-blue-600\",\n\t\tto: \"to-purple-600\",\n\t\toverlay: \"from-violet-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-pink-600\",\n\t\tvia: \"via-rose-600\",\n\t\tto: \"to-orange-600\",\n\t\toverlay: \"from-fuchsia-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-cyan-600\",\n\t\tvia: \"via-teal-600\",\n\t\tto: \"to-emerald-600\",\n\t\toverlay: \"from-blue-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-purple-600\",\n\t\tvia: \"via-fuchsia-600\",\n\t\tto: \"to-pink-600\",\n\t\toverlay: \"from-violet-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-blue-600\",\n\t\tvia: \"via-indigo-600\",\n\t\tto: \"to-purple-600\",\n\t\toverlay: \"from-cyan-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-green-600\",\n\t\tvia: \"via-emerald-600\",\n\t\tto: \"to-teal-600\",\n\t\toverlay: \"from-lime-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-rose-600\",\n\t\tvia: \"via-pink-600\",\n\t\tto: \"to-fuchsia-600\",\n\t\toverlay: \"from-pink-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-orange-600\",\n\t\tvia: \"via-pink-600\",\n\t\tto: \"to-rose-600\",\n\t\toverlay: \"from-amber-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-violet-600\",\n\t\tvia: \"via-purple-600\",\n\t\tto: \"to-fuchsia-600\",\n\t\toverlay: \"from-indigo-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-teal-600\",\n\t\tvia: \"via-cyan-600\",\n\t\tto: \"to-blue-600\",\n\t\toverlay: \"from-emerald-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-emerald-600\",\n\t\tvia: \"via-teal-600\",\n\t\tto: \"to-cyan-600\",\n\t\toverlay: \"from-green-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-indigo-700\",\n\t\tvia: \"via-purple-700\",\n\t\tto: \"to-pink-700\",\n\t\toverlay: \"from-violet-800/30\",\n\t},\n\t{\n\t\tfrom: \"from-blue-700\",\n\t\tvia: \"via-cyan-700\",\n\t\tto: \"to-teal-700\",\n\t\toverlay: \"from-indigo-800/30\",\n\t},\n\t{\n\t\tfrom: \"from-purple-700\",\n\t\tvia: \"via-fuchsia-700\",\n\t\tto: \"to-pink-700\",\n\t\toverlay: \"from-violet-800/30\",\n\t},\n\t{\n\t\tfrom: \"from-pink-700\",\n\t\tvia: \"via-rose-700\",\n\t\tto: \"to-orange-700\",\n\t\toverlay: \"from-fuchsia-800/30\",\n\t},\n\t{\n\t\tfrom: \"from-cyan-700\",\n\t\tvia: \"via-blue-700\",\n\t\tto: \"to-indigo-700\",\n\t\toverlay: \"from-teal-800/30\",\n\t},\n] as const;\n\n// Simple deterministic hash function\nfunction hashString(str: string): number {\n\tlet hash = 0;\n\tfor (let i = 0; i < str.length; i++) {\n\t\tconst char = str.charCodeAt(i);\n\t\thash = (hash << 5) - hash + char;\n\t\thash = hash & hash; // Convert to 32-bit integer\n\t}\n\treturn Math.abs(hash);\n}\n\nfunction getGradientFromTitle(title: string) {\n\tconst hash = hashString(title);\n\tconst palette = GRADIENT_PALETTES[hash % GRADIENT_PALETTES.length];\n\treturn palette;\n}\n\nexport function PostCard({ post }: { post: SerializedPost }) {\n\tconst t = useTranslate();\n\tconst { localization } = usePluginOverrides<\n\t\tBlogPluginOverrides,\n\t\tPartial\n\t>(BLOG_PLUGIN_ID, {});\n\tconst { router } = useStack();\n\tconst Link = router?.Link ?? DefaultLink;\n\tconst Image = router?.Image ?? DefaultImage;\n\tconst basePath = useBasePath();\n\tconst blogPath = `${basePath}/blog/${post.slug}`;\n\tconst postDate = formatDate(\n\t\tpost.publishedAt || post.createdAt,\n\t\t\"MMMM d, yyyy\",\n\t);\n\tconst gradient = post.image ? null : getGradientFromTitle(post.title);\n\n\treturn (\n\t\t\n\t\t\t{/* Image or Placeholder */}\n\t\t\t\n\t\t\t\t{post.image ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t{post.title}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\n\n\t\t\t{!post.published && (\n\t\t\t\t\n\t\t\t\t\t{localization?.BLOG_CARD_DRAFT_BADGE ??\n\t\t\t\t\t\tt(\"blog.card.draftBadge\", \"Draft\")}\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{post.title}\n\t\t\t\t\t\n\t\t\t\t\n\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{post.tags &&\n\t\t\t\t\t\t\tpost.tags.length > 0 &&\n\t\t\t\t\t\t\tpost.tags.map((tag) => (\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t{tag.name}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\n\t\t\n\t);\n}\n",
+ "content": "\"use client\";\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n\tCard,\n\tCardContent,\n\tCardHeader,\n\tCardTitle,\n} from \"@/components/ui/card\";\nimport {\n\tuseBasePath,\n\tusePluginOverrides,\n\tuseStack,\n\tuseTranslate,\n} from \"@btst/stack/context\";\nimport { formatPostDate } from \"./format-post-date\";\nimport type { SerializedPost } from \"../../../types\";\nimport { CalendarIcon } from \"lucide-react\";\nimport type { BlogPluginOverrides } from \"../../overrides\";\nimport { BLOG_PLUGIN_ID } from \"../../constants\";\nimport { DefaultLink, DefaultImage } from \"./defaults\";\n\n// Beautiful gradient color combinations\nconst GRADIENT_PALETTES = [\n\t{\n\t\tfrom: \"from-purple-500\",\n\t\tvia: \"via-pink-500\",\n\t\tto: \"to-orange-500\",\n\t\toverlay: \"from-blue-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-blue-500\",\n\t\tvia: \"via-cyan-500\",\n\t\tto: \"to-teal-500\",\n\t\toverlay: \"from-indigo-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-pink-500\",\n\t\tvia: \"via-rose-500\",\n\t\tto: \"to-orange-500\",\n\t\toverlay: \"from-purple-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-green-500\",\n\t\tvia: \"via-emerald-500\",\n\t\tto: \"to-teal-500\",\n\t\toverlay: \"from-cyan-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-indigo-500\",\n\t\tvia: \"via-purple-500\",\n\t\tto: \"to-pink-500\",\n\t\toverlay: \"from-violet-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-orange-500\",\n\t\tvia: \"via-amber-500\",\n\t\tto: \"to-yellow-500\",\n\t\toverlay: \"from-orange-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-cyan-500\",\n\t\tvia: \"via-blue-500\",\n\t\tto: \"to-indigo-500\",\n\t\toverlay: \"from-purple-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-violet-500\",\n\t\tvia: \"via-purple-500\",\n\t\tto: \"to-fuchsia-500\",\n\t\toverlay: \"from-pink-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-emerald-500\",\n\t\tvia: \"via-green-500\",\n\t\tto: \"to-lime-500\",\n\t\toverlay: \"from-teal-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-rose-500\",\n\t\tvia: \"via-pink-500\",\n\t\tto: \"to-fuchsia-500\",\n\t\toverlay: \"from-purple-600/50\",\n\t},\n\t{\n\t\tfrom: \"from-sky-500\",\n\t\tvia: \"via-blue-500\",\n\t\tto: \"to-indigo-600\",\n\t\toverlay: \"from-purple-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-fuchsia-600\",\n\t\tvia: \"via-purple-600\",\n\t\tto: \"to-indigo-600\",\n\t\toverlay: \"from-pink-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-teal-500\",\n\t\tvia: \"via-emerald-500\",\n\t\tto: \"to-green-600\",\n\t\toverlay: \"from-cyan-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-amber-500\",\n\t\tvia: \"via-orange-500\",\n\t\tto: \"to-pink-500\",\n\t\toverlay: \"from-yellow-600/40\",\n\t},\n\t{\n\t\tfrom: \"from-indigo-600\",\n\t\tvia: \"via-blue-600\",\n\t\tto: \"to-purple-600\",\n\t\toverlay: \"from-violet-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-pink-600\",\n\t\tvia: \"via-rose-600\",\n\t\tto: \"to-orange-600\",\n\t\toverlay: \"from-fuchsia-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-cyan-600\",\n\t\tvia: \"via-teal-600\",\n\t\tto: \"to-emerald-600\",\n\t\toverlay: \"from-blue-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-purple-600\",\n\t\tvia: \"via-fuchsia-600\",\n\t\tto: \"to-pink-600\",\n\t\toverlay: \"from-violet-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-blue-600\",\n\t\tvia: \"via-indigo-600\",\n\t\tto: \"to-purple-600\",\n\t\toverlay: \"from-cyan-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-green-600\",\n\t\tvia: \"via-emerald-600\",\n\t\tto: \"to-teal-600\",\n\t\toverlay: \"from-lime-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-rose-600\",\n\t\tvia: \"via-pink-600\",\n\t\tto: \"to-fuchsia-600\",\n\t\toverlay: \"from-pink-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-orange-600\",\n\t\tvia: \"via-pink-600\",\n\t\tto: \"to-rose-600\",\n\t\toverlay: \"from-amber-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-violet-600\",\n\t\tvia: \"via-purple-600\",\n\t\tto: \"to-fuchsia-600\",\n\t\toverlay: \"from-indigo-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-teal-600\",\n\t\tvia: \"via-cyan-600\",\n\t\tto: \"to-blue-600\",\n\t\toverlay: \"from-emerald-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-emerald-600\",\n\t\tvia: \"via-teal-600\",\n\t\tto: \"to-cyan-600\",\n\t\toverlay: \"from-green-700/40\",\n\t},\n\t{\n\t\tfrom: \"from-indigo-700\",\n\t\tvia: \"via-purple-700\",\n\t\tto: \"to-pink-700\",\n\t\toverlay: \"from-violet-800/30\",\n\t},\n\t{\n\t\tfrom: \"from-blue-700\",\n\t\tvia: \"via-cyan-700\",\n\t\tto: \"to-teal-700\",\n\t\toverlay: \"from-indigo-800/30\",\n\t},\n\t{\n\t\tfrom: \"from-purple-700\",\n\t\tvia: \"via-fuchsia-700\",\n\t\tto: \"to-pink-700\",\n\t\toverlay: \"from-violet-800/30\",\n\t},\n\t{\n\t\tfrom: \"from-pink-700\",\n\t\tvia: \"via-rose-700\",\n\t\tto: \"to-orange-700\",\n\t\toverlay: \"from-fuchsia-800/30\",\n\t},\n\t{\n\t\tfrom: \"from-cyan-700\",\n\t\tvia: \"via-blue-700\",\n\t\tto: \"to-indigo-700\",\n\t\toverlay: \"from-teal-800/30\",\n\t},\n] as const;\n\n// Simple deterministic hash function\nfunction hashString(str: string): number {\n\tlet hash = 0;\n\tfor (let i = 0; i < str.length; i++) {\n\t\tconst char = str.charCodeAt(i);\n\t\thash = (hash << 5) - hash + char;\n\t\thash = hash & hash; // Convert to 32-bit integer\n\t}\n\treturn Math.abs(hash);\n}\n\nfunction getGradientFromTitle(title: string) {\n\tconst hash = hashString(title);\n\tconst palette = GRADIENT_PALETTES[hash % GRADIENT_PALETTES.length];\n\treturn palette;\n}\n\nexport function PostCard({ post }: { post: SerializedPost }) {\n\tconst t = useTranslate();\n\tconst { localization } = usePluginOverrides<\n\t\tBlogPluginOverrides,\n\t\tPartial\n\t>(BLOG_PLUGIN_ID, {});\n\tconst { router } = useStack();\n\tconst Link = router?.Link ?? DefaultLink;\n\tconst Image = router?.Image ?? DefaultImage;\n\tconst basePath = useBasePath();\n\tconst blogPath = `${basePath}/blog/${post.slug}`;\n\tconst timestamp = post.publishedAt || post.createdAt;\n\tconst postDate = formatPostDate(timestamp);\n\tconst gradient = post.image ? null : getGradientFromTitle(post.title);\n\n\treturn (\n\t\t\n\t\t\t{/* Image or Placeholder */}\n\t\t\t\n\t\t\t\t{post.image ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t{post.title}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\n\n\t\t\t{!post.published && (\n\t\t\t\t\n\t\t\t\t\t{localization?.BLOG_CARD_DRAFT_BADGE ??\n\t\t\t\t\t\tt(\"blog.card.draftBadge\", \"Draft\")}\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{post.title}\n\t\t\t\t\t\n\t\t\t\t\n\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{post.tags &&\n\t\t\t\t\t\t\tpost.tags.length > 0 &&\n\t\t\t\t\t\t\tpost.tags.map((tag) => (\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t{tag.name}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\n\t\t\n\t);\n}\n",
"target": "src/components/btst/blog/client/components/shared/post-card.tsx"
},
{