@fullstackdatasolutions/articles 0.12.0 → 1.0.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +550 -3
  3. package/dist/index.cjs +960 -383
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +372 -20
  6. package/dist/index.d.ts +372 -20
  7. package/dist/index.js +944 -371
  8. package/dist/index.js.map +1 -1
  9. package/dist/nextjs.cjs +74 -6
  10. package/dist/nextjs.cjs.map +1 -1
  11. package/dist/nextjs.d.cts +141 -0
  12. package/dist/nextjs.d.ts +141 -0
  13. package/dist/nextjs.js +74 -6
  14. package/dist/nextjs.js.map +1 -1
  15. package/dist/server.cjs +665 -27
  16. package/dist/server.cjs.map +1 -1
  17. package/dist/server.d.cts +349 -3
  18. package/dist/server.d.ts +349 -3
  19. package/dist/server.js +643 -29
  20. package/dist/server.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/ArticleCard.tsx +37 -1
  23. package/src/ArticleContent.tsx +144 -5
  24. package/src/ArticleDetailHero.tsx +23 -0
  25. package/src/ArticleNavigation.tsx +32 -1
  26. package/src/ArticleSchemas.tsx +43 -39
  27. package/src/ArticleSocialShare.tsx +54 -10
  28. package/src/ArticlesPage.tsx +55 -5
  29. package/src/AuthorArticlesPage.tsx +308 -14
  30. package/src/AuthorCard.tsx +1 -1
  31. package/src/CategoryArticlesPage.tsx +34 -2
  32. package/src/LatestArticles.tsx +28 -1
  33. package/src/LatestArticlesSection.tsx +15 -1
  34. package/src/PaginationNav.tsx +78 -0
  35. package/src/RelatedArticlesSection.tsx +55 -0
  36. package/src/SeriesArticlesPage.tsx +66 -0
  37. package/src/__tests__/ArticleCard.test.tsx +63 -3
  38. package/src/__tests__/ArticleContent.test.tsx +143 -0
  39. package/src/__tests__/ArticleDetailHero.test.tsx +30 -0
  40. package/src/__tests__/ArticleNavigation.test.tsx +81 -3
  41. package/src/__tests__/ArticleSchemas.test.tsx +155 -81
  42. package/src/__tests__/ArticleSocialShare.test.tsx +54 -0
  43. package/src/__tests__/ArticlesPage.test.tsx +131 -0
  44. package/src/__tests__/AuthorArticlesPage.test.tsx +304 -3
  45. package/src/__tests__/CategoryArticlesPage.test.tsx +116 -1
  46. package/src/__tests__/LatestArticles.test.tsx +52 -0
  47. package/src/__tests__/LatestArticlesSection.test.tsx +28 -0
  48. package/src/__tests__/PaginationNav.test.tsx +73 -0
  49. package/src/__tests__/RelatedArticlesSection.test.tsx +132 -0
  50. package/src/__tests__/SeriesArticlesPage.test.tsx +121 -0
  51. package/src/__tests__/eventTracking.test.tsx +145 -0
  52. package/src/__tests__/events.test.ts +82 -0
  53. package/src/__tests__/markdown.test.ts +78 -1
  54. package/src/__tests__/pagination.test.ts +178 -0
  55. package/src/__tests__/seoUtils-authors.test.ts +37 -0
  56. package/src/__tests__/seoUtils.test.ts +246 -0
  57. package/src/__tests__/server-articles.test.ts +356 -1
  58. package/src/__tests__/validateArticles.test.ts +312 -0
  59. package/src/articleTypes.ts +109 -0
  60. package/src/articlesConfig.ts +37 -1
  61. package/src/eventTracking.tsx +97 -0
  62. package/src/events.ts +105 -0
  63. package/src/index.ts +26 -1
  64. package/src/markdown.ts +41 -0
  65. package/src/pagination.ts +93 -0
  66. package/src/seoUtils.ts +198 -11
  67. package/src/server-articles.ts +199 -6
  68. package/src/server.ts +46 -2
  69. package/src/validateArticles.ts +260 -0
package/dist/index.cjs CHANGED
@@ -75,10 +75,10 @@ __export(src_exports, {
75
75
  ArticleDetailHero: () => ArticleDetailHero,
76
76
  ArticleNavigation: () => ArticleNavigation,
77
77
  ArticleSEO: () => ArticleSEO,
78
- ArticleSchema: () => ArticleSchema,
79
78
  ArticleSearchBar: () => ArticleSearchBar,
80
79
  ArticleSocialShare: () => ArticleSocialShare,
81
80
  ArticleTOC: () => ArticleTOC,
81
+ ArticleViewTracker: () => ArticleViewTracker,
82
82
  ArticlesHero: () => ArticlesHero,
83
83
  ArticlesPage: () => ArticlesPage,
84
84
  AuthorArticlesPage: () => AuthorArticlesPage,
@@ -93,6 +93,7 @@ __export(src_exports, {
93
93
  CommentItem: () => CommentItem,
94
94
  CommentThread: () => CommentThread,
95
95
  CommentsSection: () => CommentsSection,
96
+ CtaViewTracker: () => CtaViewTracker,
96
97
  DEFAULT_CATEGORIES_PAGE_SIZE: () => DEFAULT_CATEGORIES_PAGE_SIZE,
97
98
  DEFAULT_LAYOUT: () => DEFAULT_LAYOUT,
98
99
  DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
@@ -100,7 +101,10 @@ __export(src_exports, {
100
101
  FeaturedArticle: () => FeaturedArticle,
101
102
  LatestArticles: () => LatestArticles,
102
103
  LatestArticlesSection: () => LatestArticlesSection,
104
+ PaginationNav: () => PaginationNav,
105
+ RelatedArticlesSection: () => RelatedArticlesSection,
103
106
  ScrollToTop: () => ScrollToTop,
107
+ SeriesArticlesPage: () => SeriesArticlesPage,
104
108
  breadcrumbsAreEnabled: () => breadcrumbsAreEnabled,
105
109
  getBreadcrumbsConfig: () => getBreadcrumbsConfig,
106
110
  useArticles: () => useArticles
@@ -297,43 +301,17 @@ function BreadcrumbSchema({ items }) {
297
301
 
298
302
  // src/ArticleSchemas.tsx
299
303
  var import_jsx_runtime3 = require("react/jsx-runtime");
300
- function getPersonSchemas(article, authors) {
304
+ function getPersonSchemas(article, authors, config) {
301
305
  let resolvedAuthors = [];
302
306
  if (authors == null ? void 0 : authors.length) {
303
307
  resolvedAuthors = [...authors];
304
308
  } else if (article.author) {
305
309
  resolvedAuthors = [{ name: article.author, slug: "", bio: "" }];
306
310
  }
307
- return resolvedAuthors.map((author) => __spreadValues(__spreadValues({
311
+ return resolvedAuthors.map((author) => __spreadValues(__spreadValues(__spreadValues({
308
312
  "@type": "Person",
309
313
  name: author.name
310
- }, author.url && { url: author.url }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }));
311
- }
312
- function ArticleSchema({
313
- article,
314
- articleUrl,
315
- siteName,
316
- showAuthor = true,
317
- authors
318
- }) {
319
- const personSchemas = getPersonSchemas(article, authors);
320
- const schema = __spreadProps(__spreadValues(__spreadValues({
321
- "@context": "https://schema.org",
322
- "@type": "Article",
323
- headline: article.title,
324
- description: article.excerpt,
325
- image: article.featuredImage
326
- }, article.date && { datePublished: new Date(article.date).toISOString() }), showAuthor && personSchemas.length > 0 && { author: personSchemas }), {
327
- publisher: { "@type": "Organization", name: siteName },
328
- mainEntityOfPage: { "@type": "WebPage", "@id": articleUrl }
329
- });
330
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
331
- "script",
332
- {
333
- type: "application/ld+json",
334
- dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
335
- }
336
- );
314
+ }, author.url && { url: author.url }), getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }));
337
315
  }
338
316
  function ArticleSEO({
339
317
  article,
@@ -341,12 +319,13 @@ function ArticleSEO({
341
319
  siteName,
342
320
  siteLogo,
343
321
  showAuthor = true,
344
- authors
322
+ authors,
323
+ config
345
324
  }) {
346
325
  const publishedAt = article.date ? new Date(article.date).toISOString() : void 0;
347
326
  const modifiedAt = article.lastmod ? new Date(article.lastmod).toISOString() : publishedAt;
348
- const personSchemas = getPersonSchemas(article, authors);
349
- const structuredData = __spreadValues(__spreadProps(__spreadValues(__spreadValues(__spreadValues({
327
+ const personSchemas = getPersonSchemas(article, authors, config);
328
+ const structuredData = __spreadValues(__spreadValues(__spreadValues(__spreadProps(__spreadValues(__spreadValues(__spreadValues({
350
329
  "@context": "https://schema.org",
351
330
  "@type": article.articleType || "Article",
352
331
  mainEntityOfPage: { "@type": "WebPage", "@id": articleUrl },
@@ -357,8 +336,9 @@ function ArticleSEO({
357
336
  "@type": "Organization",
358
337
  name: siteName
359
338
  }, siteLogo && { logo: { "@type": "ImageObject", url: siteLogo } }),
360
- description: article.excerpt
361
- }), article.series && { isPartOf: { "@type": "Blog", name: article.series } });
339
+ description: article.excerpt,
340
+ articleSection: article.category
341
+ }), article.tags && article.tags.length > 0 && { keywords: article.tags.join(", ") }), article.wordCount !== void 0 && { wordCount: article.wordCount }), article.series && { isPartOf: { "@type": "Blog", name: article.series } });
362
342
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
363
343
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
364
344
  "script",
@@ -426,16 +406,27 @@ function CollectionPageSchema({
426
406
  title,
427
407
  description,
428
408
  url,
429
- articleCount
409
+ articleCount,
410
+ items
430
411
  }) {
431
- const schema = {
412
+ const schema = __spreadValues({
432
413
  "@context": "https://schema.org",
433
414
  "@type": "CollectionPage",
434
415
  name: title,
435
416
  description,
436
417
  url,
437
418
  numberOfItems: articleCount
438
- };
419
+ }, items && items.length > 0 && {
420
+ mainEntity: {
421
+ "@type": "ItemList",
422
+ itemListElement: items.map((item) => ({
423
+ "@type": "ListItem",
424
+ position: item.position,
425
+ url: item.url,
426
+ name: item.name
427
+ }))
428
+ }
429
+ });
439
430
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
440
431
  "script",
441
432
  {
@@ -570,20 +561,98 @@ function FeaturedArticle({ article, showAuthor = true }) {
570
561
  }
571
562
 
572
563
  // src/LatestArticlesSection.tsx
573
- var import_lucide_react4 = require("lucide-react");
564
+ var import_lucide_react6 = require("lucide-react");
574
565
 
575
566
  // src/LatestArticles.tsx
576
567
  var import_react2 = require("react");
577
568
 
578
569
  // src/ArticleCard.tsx
570
+ var import_image4 = __toESM(require("next/image"), 1);
571
+ var import_link6 = __toESM(require("next/link"), 1);
572
+ var import_lucide_react4 = require("lucide-react");
573
+
574
+ // src/AuthorCard.tsx
579
575
  var import_image3 = __toESM(require("next/image"), 1);
580
576
  var import_link5 = __toESM(require("next/link"), 1);
581
577
  var import_lucide_react3 = require("lucide-react");
582
578
  var import_jsx_runtime7 = require("react/jsx-runtime");
583
- function ArticleCard({ article }) {
584
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rounded-lg border border-border bg-card text-card-foreground shadow-sm hover:shadow-lg transition-shadow duration-300", children: [
585
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "p-0", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
579
+ function getInitials(name) {
580
+ return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
581
+ }
582
+ function AuthorCard({
583
+ author,
584
+ linkToPage = true,
585
+ showBio = false,
586
+ showSocial = false
587
+ }) {
588
+ const avatar = getAuthorAvatar(author);
589
+ const name = linkToPage ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_link5.default, { href: getAuthorUrl(author), className: "font-medium text-foreground hover:text-primary", children: author.name }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-medium text-foreground", children: author.name });
590
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-start gap-3 rounded-lg border border-border bg-card p-4 text-card-foreground", children: [
591
+ avatar ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
586
592
  import_image3.default,
593
+ {
594
+ src: avatar,
595
+ alt: author.name,
596
+ width: 48,
597
+ height: 48,
598
+ className: "h-12 w-12 rounded-full object-cover"
599
+ }
600
+ ) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-muted text-sm font-semibold text-muted-foreground", children: getInitials(author.name) }),
601
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "min-w-0 flex-1", children: [
602
+ name,
603
+ showBio && author.bio && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "mt-1 line-clamp-2 text-sm text-muted-foreground", children: author.bio }),
604
+ showSocial && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AuthorSocialLinks, { author })
605
+ ] })
606
+ ] });
607
+ }
608
+ var SOCIAL_ICONS = {
609
+ Website: import_lucide_react3.Globe,
610
+ Facebook: import_lucide_react3.Facebook,
611
+ Twitter: import_lucide_react3.Twitter,
612
+ X: import_lucide_react3.Twitter,
613
+ LinkedIn: import_lucide_react3.Linkedin,
614
+ Instagram: import_lucide_react3.Instagram,
615
+ YouTube: import_lucide_react3.Youtube,
616
+ GitHub: import_lucide_react3.Github,
617
+ Newsletter: import_lucide_react3.Mail
618
+ };
619
+ function getSocialIcon(label) {
620
+ var _a;
621
+ return (_a = SOCIAL_ICONS[label]) != null ? _a : import_lucide_react3.MessageCircle;
622
+ }
623
+ function AuthorSocialLinks({ author }) {
624
+ const links = getAuthorSocialLinks(author);
625
+ if (links.length === 0) return null;
626
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mt-3 flex items-center gap-2", children: links.map(({ href, label }) => {
627
+ const Icon = getSocialIcon(label);
628
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
629
+ import_link5.default,
630
+ {
631
+ href,
632
+ className: "inline-flex h-8 w-8 items-center justify-center rounded-md border border-border text-muted-foreground hover:bg-accent hover:text-accent-foreground",
633
+ "aria-label": `${author.name} on ${label}`,
634
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { className: "h-4 w-4" })
635
+ },
636
+ `${label}-${href}`
637
+ );
638
+ }) });
639
+ }
640
+
641
+ // src/events.ts
642
+ function emitArticleEvent(handler, event) {
643
+ if (!handler) return;
644
+ try {
645
+ handler(__spreadProps(__spreadValues({}, event), { timestamp: Date.now() }));
646
+ } catch (e) {
647
+ }
648
+ }
649
+
650
+ // src/ArticleCard.tsx
651
+ var import_jsx_runtime8 = require("react/jsx-runtime");
652
+ function ArticleCard({ article, config }) {
653
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rounded-lg border border-border bg-card text-card-foreground shadow-sm hover:shadow-lg transition-shadow duration-300", children: [
654
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "p-0", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
655
+ import_image4.default,
587
656
  {
588
657
  src: article.featuredImage,
589
658
  alt: article.title,
@@ -592,35 +661,67 @@ function ArticleCard({ article }) {
592
661
  height: 200
593
662
  }
594
663
  ) }),
595
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "p-6", children: [
596
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex gap-2 mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded", children: article.category }) }),
597
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { className: "text-lg font-semibold leading-none tracking-tight mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
598
- import_link5.default,
664
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "p-6", children: [
665
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex gap-2 mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded", children: article.category }) }),
666
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "text-lg font-semibold leading-none tracking-tight mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
667
+ import_link6.default,
599
668
  {
600
669
  href: `/articles/${article.slug}`,
601
670
  className: "hover:text-indigo-600 transition-colors",
602
671
  children: article.title
603
672
  }
604
673
  ) }),
605
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-muted-foreground mb-4 line-clamp-3", children: article.excerpt }),
606
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between text-sm text-muted-foreground border-t pt-4", children: [
607
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-3", children: [
608
- article.date && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
609
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Calendar, { className: "h-3 w-3" }),
610
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: article.date })
674
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-muted-foreground mb-4 line-clamp-3", children: article.excerpt }),
675
+ article.authorSlug && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
676
+ import_link6.default,
677
+ {
678
+ href: `/articles/authors/${article.authorSlug}`,
679
+ className: "mb-4 flex items-center gap-2 text-sm text-muted-foreground hover:text-primary transition-colors",
680
+ onClick: () => emitArticleEvent(config == null ? void 0 : config.onEvent, {
681
+ name: "author_clicked",
682
+ articleSlug: article.slug,
683
+ authorSlug: article.authorSlug
684
+ }),
685
+ children: [
686
+ article.authorAvatar ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
687
+ import_image4.default,
688
+ {
689
+ src: article.authorAvatar,
690
+ alt: "",
691
+ width: 24,
692
+ height: 24,
693
+ className: "h-6 w-6 rounded-full object-cover"
694
+ }
695
+ ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
696
+ "span",
697
+ {
698
+ "aria-hidden": "true",
699
+ className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground",
700
+ children: getInitials(article.author)
701
+ }
702
+ ),
703
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: article.author })
704
+ ]
705
+ }
706
+ ),
707
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between text-sm text-muted-foreground border-t pt-4", children: [
708
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-3", children: [
709
+ article.date && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2", children: [
710
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Calendar, { className: "h-3 w-3" }),
711
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: article.date })
611
712
  ] }),
612
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
613
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Clock, { className: "h-3 w-3" }),
614
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: article.readTime })
713
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2", children: [
714
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Clock, { className: "h-3 w-3" }),
715
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: article.readTime })
615
716
  ] })
616
717
  ] }),
617
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
618
- import_link5.default,
718
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
719
+ import_link6.default,
619
720
  {
620
721
  href: `/articles/${article.slug}`,
621
722
  className: "inline-flex items-center justify-center h-8 w-8 p-0 rounded-md hover:bg-accent transition-colors",
622
723
  "aria-label": `Read ${article.title}`,
623
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.ArrowRight, { className: "h-4 w-4" })
724
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.ArrowRight, { className: "h-4 w-4" })
624
725
  }
625
726
  )
626
727
  ] })
@@ -628,18 +729,114 @@ function ArticleCard({ article }) {
628
729
  ] });
629
730
  }
630
731
 
732
+ // src/PaginationNav.tsx
733
+ var import_link7 = __toESM(require("next/link"), 1);
734
+ var import_lucide_react5 = require("lucide-react");
735
+
736
+ // src/pagination.ts
737
+ function buildPageUrl(basePath, page) {
738
+ const base = basePath.replace(/\/$/, "");
739
+ return page > 1 ? `${base}/page/${page}` : base;
740
+ }
741
+ function buildPaginationLinks(basePath, page, totalPages) {
742
+ return {
743
+ canonicalUrl: buildPageUrl(basePath, page),
744
+ prevUrl: page > 1 ? buildPageUrl(basePath, page - 1) : null,
745
+ nextUrl: page < totalPages ? buildPageUrl(basePath, page + 1) : null
746
+ };
747
+ }
748
+
749
+ // src/PaginationNav.tsx
750
+ var import_jsx_runtime9 = require("react/jsx-runtime");
751
+ function PaginationNav({ basePath, page, totalPages }) {
752
+ if (totalPages <= 1) return null;
753
+ const { prevUrl, nextUrl } = buildPaginationLinks(basePath, page, totalPages);
754
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
755
+ "nav",
756
+ {
757
+ className: "mt-10 flex items-center justify-center gap-4",
758
+ "aria-label": "Article listing pagination",
759
+ children: [
760
+ prevUrl && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("link", { rel: "prev", href: prevUrl }),
761
+ nextUrl && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("link", { rel: "next", href: nextUrl }),
762
+ prevUrl ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
763
+ import_link7.default,
764
+ {
765
+ href: prevUrl,
766
+ className: "inline-flex items-center gap-1 px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors",
767
+ children: [
768
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react5.ChevronLeft, { className: "h-4 w-4", "aria-hidden": "true" }),
769
+ "Previous"
770
+ ]
771
+ }
772
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
773
+ "span",
774
+ {
775
+ "aria-hidden": "true",
776
+ className: "inline-flex items-center gap-1 px-4 py-2 rounded-md font-medium text-sm text-muted-foreground opacity-50",
777
+ children: [
778
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react5.ChevronLeft, { className: "h-4 w-4" }),
779
+ "Previous"
780
+ ]
781
+ }
782
+ ),
783
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "text-sm text-muted-foreground", children: [
784
+ "Page ",
785
+ page,
786
+ " of ",
787
+ totalPages
788
+ ] }),
789
+ nextUrl ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
790
+ import_link7.default,
791
+ {
792
+ href: nextUrl,
793
+ className: "inline-flex items-center gap-1 px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors",
794
+ children: [
795
+ "Next",
796
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react5.ChevronRight, { className: "h-4 w-4", "aria-hidden": "true" })
797
+ ]
798
+ }
799
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
800
+ "span",
801
+ {
802
+ "aria-hidden": "true",
803
+ className: "inline-flex items-center gap-1 px-4 py-2 rounded-md font-medium text-sm text-muted-foreground opacity-50",
804
+ children: [
805
+ "Next",
806
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react5.ChevronRight, { className: "h-4 w-4" })
807
+ ]
808
+ }
809
+ )
810
+ ]
811
+ }
812
+ );
813
+ }
814
+
631
815
  // src/LatestArticles.tsx
632
- var import_jsx_runtime8 = require("react/jsx-runtime");
633
- function LatestArticles({ articles, pageSize }) {
816
+ var import_jsx_runtime10 = require("react/jsx-runtime");
817
+ function LatestArticles({ articles, pageSize, pagination }) {
634
818
  const [visibleCount, setVisibleCount] = (0, import_react2.useState)(pageSize);
635
819
  (0, import_react2.useEffect)(() => {
636
820
  setVisibleCount(pageSize);
637
821
  }, [articles, pageSize]);
822
+ if (pagination) {
823
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
824
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-8", children: articles.map((article) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ArticleCard, { article }, article.slug)) }),
825
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
826
+ PaginationNav,
827
+ {
828
+ basePath: pagination.basePath,
829
+ page: pagination.page,
830
+ totalPages: pagination.totalPages
831
+ }
832
+ )
833
+ ] });
834
+ }
638
835
  const visible = articles.slice(0, visibleCount);
639
836
  const hasMore = visibleCount < articles.length;
640
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
641
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-8", children: visible.map((article) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ArticleCard, { article }, article.slug)) }),
642
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mt-10 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
837
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
838
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-8", children: visible.map((article) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ArticleCard, { article }, article.slug)) }),
839
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-10 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
643
840
  "button",
644
841
  {
645
842
  type: "button",
@@ -652,35 +849,36 @@ function LatestArticles({ articles, pageSize }) {
652
849
  }
653
850
 
654
851
  // src/LatestArticlesSection.tsx
655
- var import_jsx_runtime9 = require("react/jsx-runtime");
852
+ var import_jsx_runtime11 = require("react/jsx-runtime");
656
853
  function DescriptionText({
657
854
  searchQuery,
658
855
  count
659
856
  }) {
660
857
  const pluralSuffix = count === 1 ? "" : "s";
661
858
  const text = searchQuery ? `Found ${count} article${pluralSuffix} matching your search.` : "Stay informed with our latest insights on voter engagement, campaign strategies, and civic technology.";
662
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-lg text-muted-foreground", children: text });
859
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-lg text-muted-foreground", children: text });
663
860
  }
664
861
  function LatestArticlesSection({
665
862
  articles,
666
863
  searchQuery,
667
864
  onClearSearch,
668
- pageSize = 6
865
+ pageSize = 6,
866
+ pagination
669
867
  }) {
670
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("section", { id: "latest", className: "py-16 bg-muted/50", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
671
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "text-center mb-12", children: [
672
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { className: "text-3xl font-bold text-foreground mb-4", children: searchQuery ? "Search Results" : "Latest Articles" }),
673
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DescriptionText, { searchQuery, count: articles.length })
868
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("section", { id: "latest", className: "py-16 bg-muted/50", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
869
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "text-center mb-12", children: [
870
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { className: "text-3xl font-bold text-foreground mb-4", children: searchQuery ? "Search Results" : "Latest Articles" }),
871
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DescriptionText, { searchQuery, count: articles.length })
674
872
  ] }),
675
- articles.length === 0 && searchQuery ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "text-center py-12", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "max-w-md mx-auto", children: [
676
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react4.Search, { className: "h-12 w-12 text-muted-foreground mx-auto mb-4" }),
677
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { className: "text-lg font-semibold text-foreground mb-2", children: "No articles found" }),
678
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("p", { className: "text-muted-foreground mb-4", children: [
873
+ articles.length === 0 && searchQuery ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "text-center py-12", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "max-w-md mx-auto", children: [
874
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react6.Search, { className: "h-12 w-12 text-muted-foreground mx-auto mb-4" }),
875
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { className: "text-lg font-semibold text-foreground mb-2", children: "No articles found" }),
876
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("p", { className: "text-muted-foreground mb-4", children: [
679
877
  `We couldn't find any articles matching "`,
680
878
  searchQuery,
681
879
  '". Try adjusting your search terms.'
682
880
  ] }),
683
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
881
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
684
882
  "button",
685
883
  {
686
884
  type: "button",
@@ -689,7 +887,15 @@ function LatestArticlesSection({
689
887
  children: "Clear search"
690
888
  }
691
889
  )
692
- ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LatestArticles, { articles, pageSize }, searchQuery)
890
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
891
+ LatestArticles,
892
+ {
893
+ articles,
894
+ pageSize,
895
+ pagination: searchQuery ? void 0 : pagination
896
+ },
897
+ searchQuery
898
+ )
693
899
  ] }) });
694
900
  }
695
901
 
@@ -782,7 +988,7 @@ function useArticles(initialArticles, initialCategories) {
782
988
  }
783
989
 
784
990
  // src/ArticlesPage.tsx
785
- var import_jsx_runtime10 = require("react/jsx-runtime");
991
+ var import_jsx_runtime12 = require("react/jsx-runtime");
786
992
  function buildThemeVars(theme) {
787
993
  if (!theme) return {};
788
994
  const vars = {};
@@ -811,11 +1017,12 @@ function renderSection(section, ctx, config) {
811
1017
  error,
812
1018
  handleSearch,
813
1019
  pageSize,
814
- categoriesPageSize
1020
+ categoriesPageSize,
1021
+ pagination
815
1022
  } = ctx;
816
1023
  switch (section) {
817
1024
  case "hero":
818
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1025
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
819
1026
  ArticlesHero,
820
1027
  {
821
1028
  title: (_a = config.hero) == null ? void 0 : _a.title,
@@ -824,7 +1031,7 @@ function renderSection(section, ctx, config) {
824
1031
  "hero"
825
1032
  );
826
1033
  case "search":
827
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1034
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
828
1035
  ArticleSearchBar,
829
1036
  {
830
1037
  value: searchQuery,
@@ -835,7 +1042,7 @@ function renderSection(section, ctx, config) {
835
1042
  "search"
836
1043
  );
837
1044
  case "featured":
838
- return articles.length > 0 && !searchQuery ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1045
+ return articles.length > 0 && !searchQuery ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
839
1046
  FeaturedArticle,
840
1047
  {
841
1048
  article: articles[0],
@@ -845,16 +1052,16 @@ function renderSection(section, ctx, config) {
845
1052
  ) : null;
846
1053
  case "latest":
847
1054
  if (loading && articles.length === 0) {
848
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("section", { id: "latest", className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center py-24", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "text-center", children: [
849
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4" }),
850
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-muted-foreground", children: "Loading articles..." })
1055
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("section", { id: "latest", className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center py-24", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "text-center", children: [
1056
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4" }),
1057
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-muted-foreground", children: "Loading articles..." })
851
1058
  ] }) }) }, "latest");
852
1059
  }
853
1060
  if (error) {
854
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("section", { id: "latest", className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6 max-w-md text-center", children: [
855
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-lg font-semibold text-red-800 mb-2", children: "Error Loading Articles" }),
856
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-red-600 mb-4", children: error }),
857
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1061
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("section", { id: "latest", className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6 max-w-md text-center", children: [
1062
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h3", { className: "text-lg font-semibold text-red-800 mb-2", children: "Error Loading Articles" }),
1063
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-red-600 mb-4", children: error }),
1064
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
858
1065
  "button",
859
1066
  {
860
1067
  type: "button",
@@ -865,18 +1072,19 @@ function renderSection(section, ctx, config) {
865
1072
  )
866
1073
  ] }) }) }, "latest");
867
1074
  }
868
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1075
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
869
1076
  LatestArticlesSection,
870
1077
  {
871
1078
  articles: displayedArticles,
872
1079
  searchQuery,
873
1080
  onClearSearch: () => handleSearch(""),
874
- pageSize
1081
+ pageSize,
1082
+ pagination
875
1083
  },
876
1084
  "latest"
877
1085
  );
878
1086
  case "categories":
879
- return searchQuery ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1087
+ return searchQuery ? null : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
880
1088
  ArticleCategoryGrid,
881
1089
  {
882
1090
  categories,
@@ -892,7 +1100,10 @@ function renderSection(section, ctx, config) {
892
1100
  function ArticlesPage({
893
1101
  config,
894
1102
  initialArticles,
895
- initialCategories
1103
+ initialCategories,
1104
+ page,
1105
+ totalPages,
1106
+ totalCount
896
1107
  }) {
897
1108
  var _a, _b, _c;
898
1109
  const state = useArticles(initialArticles, initialCategories);
@@ -900,19 +1111,34 @@ function ArticlesPage({
900
1111
  const pageSize = (_b = config.pageSize) != null ? _b : DEFAULT_PAGE_SIZE;
901
1112
  const categoriesPageSize = (_c = config.categoriesPageSize) != null ? _c : DEFAULT_CATEGORIES_PAGE_SIZE;
902
1113
  const themeVars = buildThemeVars(config.theme);
903
- const hasFeatured = layout.includes("featured");
1114
+ const isPagesMode = config.listingPagination === "pages" && page !== void 0 && totalPages !== void 0;
1115
+ const currentPage = page != null ? page : 1;
1116
+ const effectiveLayout = isPagesMode && currentPage > 1 ? layout.filter((section) => section !== "featured") : layout;
1117
+ const hasFeatured = effectiveLayout.includes("featured");
904
1118
  const articlesWithoutFeatured = hasFeatured ? state.articles.slice(1) : state.articles;
905
1119
  const displayedArticles = state.searchQuery ? state.articles : articlesWithoutFeatured;
906
- const ctx = __spreadProps(__spreadValues({}, state), { displayedArticles, pageSize, categoriesPageSize });
907
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: themeVars, children: [
908
- layout.map((section) => renderSection(section, ctx, config)),
909
- state.articles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1120
+ const pagination = isPagesMode ? { page: currentPage, totalPages, basePath: "/articles" } : void 0;
1121
+ const ctx = __spreadProps(__spreadValues({}, state), {
1122
+ displayedArticles,
1123
+ pageSize,
1124
+ categoriesPageSize,
1125
+ pagination
1126
+ });
1127
+ const articleCount = totalCount != null ? totalCount : state.articles.length;
1128
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: themeVars, children: [
1129
+ effectiveLayout.map((section) => renderSection(section, ctx, config)),
1130
+ state.articles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
910
1131
  CollectionPageSchema,
911
1132
  {
912
1133
  title: `Articles | ${config.siteName}`,
913
- description: `Browse all ${state.articles.length} articles on ${config.siteName}.`,
1134
+ description: `Browse all ${articleCount} articles on ${config.siteName}.`,
914
1135
  url: `${config.siteUrl}/articles`,
915
- articleCount: state.articles.length
1136
+ articleCount,
1137
+ items: displayedArticles.slice(0, pageSize).map((article, index) => ({
1138
+ position: index + 1,
1139
+ url: `${config.siteUrl}/articles/${article.slug}`,
1140
+ name: article.title
1141
+ }))
916
1142
  }
917
1143
  )
918
1144
  ] });
@@ -920,10 +1146,10 @@ function ArticlesPage({
920
1146
 
921
1147
  // src/ArticleDetailHero.tsx
922
1148
  var import_react4 = require("react");
923
- var import_image4 = __toESM(require("next/image"), 1);
924
- var import_link6 = __toESM(require("next/link"), 1);
925
- var import_lucide_react5 = require("lucide-react");
926
- var import_jsx_runtime11 = require("react/jsx-runtime");
1149
+ var import_image5 = __toESM(require("next/image"), 1);
1150
+ var import_link8 = __toESM(require("next/link"), 1);
1151
+ var import_lucide_react7 = require("lucide-react");
1152
+ var import_jsx_runtime13 = require("react/jsx-runtime");
927
1153
  function toSlug(cat) {
928
1154
  return cat.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
929
1155
  }
@@ -936,7 +1162,7 @@ function ArticleDetailHero({
936
1162
  }) {
937
1163
  const showConfiguredAuthors = showAuthor && authors.length > 0;
938
1164
  const showLegacyAuthor = showAuthor && authors.length === 0 && article.author.trim().length > 0;
939
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1165
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
940
1166
  "section",
941
1167
  {
942
1168
  style: {
@@ -948,8 +1174,8 @@ function ArticleDetailHero({
948
1174
  overflow: "hidden"
949
1175
  },
950
1176
  children: [
951
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
952
- import_image4.default,
1177
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1178
+ import_image5.default,
953
1179
  {
954
1180
  src: article.featuredImage,
955
1181
  alt: article.title,
@@ -960,7 +1186,7 @@ function ArticleDetailHero({
960
1186
  priority: true
961
1187
  }
962
1188
  ),
963
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1189
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
964
1190
  "div",
965
1191
  {
966
1192
  style: {
@@ -970,7 +1196,7 @@ function ArticleDetailHero({
970
1196
  }
971
1197
  }
972
1198
  ),
973
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1199
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
974
1200
  "div",
975
1201
  {
976
1202
  style: {
@@ -984,7 +1210,7 @@ function ArticleDetailHero({
984
1210
  width: "100%"
985
1211
  },
986
1212
  children: [
987
- article.categories && article.categories.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1213
+ article.categories && article.categories.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
988
1214
  "div",
989
1215
  {
990
1216
  style: {
@@ -995,15 +1221,15 @@ function ArticleDetailHero({
995
1221
  marginBottom: "1rem"
996
1222
  },
997
1223
  children: article.categories.slice(0, 4).map(
998
- (cat) => categoryBasePath ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
999
- import_link6.default,
1224
+ (cat) => categoryBasePath ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1225
+ import_link8.default,
1000
1226
  {
1001
1227
  href: `${categoryBasePath}/${toSlug(cat)}`,
1002
1228
  className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full hover:bg-white/30 transition-colors",
1003
1229
  children: cat
1004
1230
  },
1005
1231
  cat
1006
- ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1232
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1007
1233
  "span",
1008
1234
  {
1009
1235
  className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full",
@@ -1014,8 +1240,8 @@ function ArticleDetailHero({
1014
1240
  )
1015
1241
  }
1016
1242
  ),
1017
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h1", { className: "text-4xl md:text-5xl font-bold mb-4", children: article.title }),
1018
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1243
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h1", { className: "text-4xl md:text-5xl font-bold mb-4", children: article.title }),
1244
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1019
1245
  "div",
1020
1246
  {
1021
1247
  style: {
@@ -1027,23 +1253,23 @@ function ArticleDetailHero({
1027
1253
  color: "rgba(255,255,255,0.8)"
1028
1254
  },
1029
1255
  children: [
1030
- showDate && article.date && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1031
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react5.Calendar, { className: "h-4 w-4" }),
1032
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: article.date })
1256
+ showDate && article.date && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1257
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.Calendar, { className: "h-4 w-4" }),
1258
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: article.date })
1033
1259
  ] }),
1034
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1035
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react5.Clock, { className: "h-4 w-4" }),
1036
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: article.readTime })
1260
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1261
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.Clock, { className: "h-4 w-4" }),
1262
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: article.readTime })
1037
1263
  ] }),
1038
- showConfiguredAuthors && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1039
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react5.User, { className: "h-4 w-4" }),
1040
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
1264
+ showConfiguredAuthors && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1265
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.User, { className: "h-4 w-4" }),
1266
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
1041
1267
  "By",
1042
1268
  " ",
1043
- authors.map((author, index) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react4.Fragment, { children: [
1269
+ authors.map((author, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react4.Fragment, { children: [
1044
1270
  index > 0 && ", ",
1045
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1046
- import_link6.default,
1271
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1272
+ import_link8.default,
1047
1273
  {
1048
1274
  href: `/articles/authors/${author.slug}`,
1049
1275
  className: "font-medium text-white underline-offset-4 hover:underline",
@@ -1053,15 +1279,26 @@ function ArticleDetailHero({
1053
1279
  ] }, author.slug))
1054
1280
  ] })
1055
1281
  ] }),
1056
- showLegacyAuthor && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1057
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react5.User, { className: "h-4 w-4" }),
1058
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
1282
+ showLegacyAuthor && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1283
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.User, { className: "h-4 w-4" }),
1284
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
1059
1285
  "By ",
1060
1286
  article.author
1061
1287
  ] })
1062
1288
  ] })
1063
1289
  ]
1064
1290
  }
1291
+ ),
1292
+ showConfiguredAuthors && authors.length === 1 && authors[0].promise && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1293
+ "p",
1294
+ {
1295
+ style: {
1296
+ marginTop: "0.75rem",
1297
+ fontSize: "0.9375rem",
1298
+ color: "rgba(255,255,255,0.85)"
1299
+ },
1300
+ children: authors[0].promise
1301
+ }
1065
1302
  )
1066
1303
  ]
1067
1304
  }
@@ -1072,9 +1309,9 @@ function ArticleDetailHero({
1072
1309
  }
1073
1310
 
1074
1311
  // src/CategoryArticlesPage.tsx
1075
- var import_image5 = __toESM(require("next/image"), 1);
1076
- var import_link7 = __toESM(require("next/link"), 1);
1077
- var import_jsx_runtime12 = require("react/jsx-runtime");
1312
+ var import_image6 = __toESM(require("next/image"), 1);
1313
+ var import_link9 = __toESM(require("next/link"), 1);
1314
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1078
1315
  function getCategoryDescription(config, slug, categoryName) {
1079
1316
  var _a;
1080
1317
  const raw = (_a = config.categoryDescriptions) == null ? void 0 : _a[slug];
@@ -1116,7 +1353,14 @@ function buildCategoryBreadcrumbEntry(entry, context) {
1116
1353
  }
1117
1354
  return [{ name: context.categoryName }];
1118
1355
  }
1119
- function CategoryArticlesPage({ category, articles, config }) {
1356
+ function CategoryArticlesPage({
1357
+ category,
1358
+ articles,
1359
+ config,
1360
+ page,
1361
+ totalPages,
1362
+ totalCount
1363
+ }) {
1120
1364
  var _a;
1121
1365
  if (articles.length === 0) return null;
1122
1366
  const categoryName = articles[0].category;
@@ -1125,8 +1369,25 @@ function CategoryArticlesPage({ category, articles, config }) {
1125
1369
  const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1126
1370
  const breadcrumbConfig = getBreadcrumbsConfig(config);
1127
1371
  const breadcrumbItems = buildCategoryBreadcrumbItems(config, categoryName);
1128
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
1129
- breadcrumbsAreEnabled(config) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1372
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1373
+ const articleCount = totalCount != null ? totalCount : articles.length;
1374
+ const pagination = config.listingPagination === "pages" && page !== void 0 && totalPages !== void 0 ? { page, totalPages, basePath: `/articles/category/${category}` } : void 0;
1375
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { children: [
1376
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1377
+ CollectionPageSchema,
1378
+ {
1379
+ title: `${categoryName} | ${config.siteName}`,
1380
+ description: description.short,
1381
+ url: `${siteUrl}/articles/category/${category}`,
1382
+ articleCount,
1383
+ items: articles.slice(0, pageSize).map((article, index) => ({
1384
+ position: index + 1,
1385
+ url: `${siteUrl}/articles/${article.slug}`,
1386
+ name: article.title
1387
+ }))
1388
+ }
1389
+ ),
1390
+ breadcrumbsAreEnabled(config) && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1130
1391
  Breadcrumb,
1131
1392
  {
1132
1393
  items: breadcrumbItems,
@@ -1134,7 +1395,7 @@ function CategoryArticlesPage({ category, articles, config }) {
1134
1395
  showSchema: breadcrumbConfig.showSchema !== false
1135
1396
  }
1136
1397
  ),
1137
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1398
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1138
1399
  "section",
1139
1400
  {
1140
1401
  style: {
@@ -1146,8 +1407,8 @@ function CategoryArticlesPage({ category, articles, config }) {
1146
1407
  overflow: "hidden"
1147
1408
  },
1148
1409
  children: [
1149
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1150
- import_image5.default,
1410
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1411
+ import_image6.default,
1151
1412
  {
1152
1413
  src: heroImage,
1153
1414
  alt: categoryName,
@@ -1157,8 +1418,8 @@ function CategoryArticlesPage({ category, articles, config }) {
1157
1418
  priority: true
1158
1419
  }
1159
1420
  ),
1160
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.6)" } }),
1161
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1421
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.6)" } }),
1422
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1162
1423
  "div",
1163
1424
  {
1164
1425
  style: {
@@ -1169,10 +1430,10 @@ function CategoryArticlesPage({ category, articles, config }) {
1169
1430
  padding: "0 1rem"
1170
1431
  },
1171
1432
  children: [
1172
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
1173
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
1174
- description.long && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
1175
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-lg opacity-70 mt-2", children: [
1433
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
1434
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
1435
+ description.long && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
1436
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { className: "text-lg opacity-70 mt-2", children: [
1176
1437
  articles.length,
1177
1438
  " article",
1178
1439
  articles.length === 1 ? "" : "s"
@@ -1183,140 +1444,75 @@ function CategoryArticlesPage({ category, articles, config }) {
1183
1444
  ]
1184
1445
  }
1185
1446
  ),
1186
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("section", { className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
1187
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mb-8 flex items-center justify-between", children: [
1188
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_link7.default, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
1189
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm text-muted-foreground", children: description.short })
1447
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("section", { className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
1448
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "mb-8 flex items-center justify-between", children: [
1449
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_link9.default, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
1450
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm text-muted-foreground", children: description.short })
1190
1451
  ] }),
1191
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(LatestArticles, { articles, pageSize })
1452
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(LatestArticles, { articles, pageSize, pagination })
1192
1453
  ] }) })
1193
1454
  ] });
1194
1455
  }
1195
1456
 
1196
- // src/AuthorArticlesPage.tsx
1197
- var import_link8 = __toESM(require("next/link"), 1);
1198
- var import_jsx_runtime13 = require("react/jsx-runtime");
1199
- function getPersonSchema(author, config, articleCount) {
1200
- var _a, _b;
1201
- return __spreadProps(__spreadValues(__spreadValues({
1202
- "@context": "https://schema.org",
1203
- "@type": "Person",
1204
- name: author.name,
1205
- description: author.bio,
1206
- url: (_a = author.url) != null ? _a : getAuthorUrl(author)
1207
- }, getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }), {
1208
- knowsAbout: config.siteName,
1209
- mainEntityOfPage: (_b = author.url) != null ? _b : `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`,
1210
- interactionStatistic: {
1211
- "@type": "InteractionCounter",
1212
- interactionType: "https://schema.org/WriteAction",
1213
- userInteractionCount: articleCount
1214
- }
1215
- });
1216
- }
1217
- function AuthorArticlesPage({ author, articles, config }) {
1457
+ // src/SeriesArticlesPage.tsx
1458
+ var import_link10 = __toESM(require("next/link"), 1);
1459
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1460
+ function SeriesArticlesPage({ seriesSlug, articles, config }) {
1218
1461
  var _a;
1219
- const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1220
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
1221
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1222
- "script",
1462
+ if (articles.length === 0) return null;
1463
+ const seriesName = (_a = articles[0].series) != null ? _a : seriesSlug;
1464
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1465
+ const seriesUrl = `${siteUrl}/articles/series/${seriesSlug}`;
1466
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
1467
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1468
+ CollectionPageSchema,
1223
1469
  {
1224
- type: "application/ld+json",
1225
- dangerouslySetInnerHTML: {
1226
- __html: JSON.stringify(getPersonSchema(author, config, articles.length))
1227
- }
1470
+ title: `${seriesName} | ${config.siteName}`,
1471
+ description: `Follow the ${seriesName} series on ${config.siteName}.`,
1472
+ url: seriesUrl,
1473
+ articleCount: articles.length,
1474
+ items: articles.map((article, index) => ({
1475
+ position: index + 1,
1476
+ url: `${siteUrl}/articles/${article.slug}`,
1477
+ name: article.title
1478
+ }))
1228
1479
  }
1229
1480
  ),
1230
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("section", { className: "bg-background py-16", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", children: [
1231
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "mb-8 flex items-center justify-between gap-4", children: [
1232
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_link8.default, { href: "/articles", className: "text-sm text-primary hover:underline", children: "All Articles" }),
1233
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
1234
- "Articles by ",
1235
- author.name
1481
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("section", { className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
1482
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "mb-8 flex items-center justify-between", children: [
1483
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_link10.default, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
1484
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
1485
+ articles.length,
1486
+ " article",
1487
+ articles.length === 1 ? "" : "s",
1488
+ " in this series"
1236
1489
  ] })
1237
1490
  ] }),
1238
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(LatestArticles, { articles, pageSize })
1491
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h1", { className: "text-3xl font-bold text-foreground mb-8", children: seriesName }),
1492
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1493
+ LatestArticles,
1494
+ {
1495
+ articles,
1496
+ pageSize: Math.max(articles.length, DEFAULT_PAGE_SIZE)
1497
+ }
1498
+ )
1239
1499
  ] }) })
1240
1500
  ] });
1241
1501
  }
1242
1502
 
1243
- // src/AuthorCard.tsx
1244
- var import_image6 = __toESM(require("next/image"), 1);
1245
- var import_link9 = __toESM(require("next/link"), 1);
1246
- var import_lucide_react6 = require("lucide-react");
1247
- var import_jsx_runtime14 = require("react/jsx-runtime");
1248
- function getInitials(name) {
1249
- return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
1250
- }
1251
- function AuthorCard({
1252
- author,
1253
- linkToPage = true,
1254
- showBio = false,
1255
- showSocial = false
1256
- }) {
1257
- const avatar = getAuthorAvatar(author);
1258
- const name = linkToPage ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_link9.default, { href: getAuthorUrl(author), className: "font-medium text-foreground hover:text-primary", children: author.name }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "font-medium text-foreground", children: author.name });
1259
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-start gap-3 rounded-lg border border-border bg-card p-4 text-card-foreground", children: [
1260
- avatar ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1261
- import_image6.default,
1262
- {
1263
- src: avatar,
1264
- alt: author.name,
1265
- width: 48,
1266
- height: 48,
1267
- className: "h-12 w-12 rounded-full object-cover"
1268
- }
1269
- ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-muted text-sm font-semibold text-muted-foreground", children: getInitials(author.name) }),
1270
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "min-w-0 flex-1", children: [
1271
- name,
1272
- showBio && author.bio && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "mt-1 line-clamp-2 text-sm text-muted-foreground", children: author.bio }),
1273
- showSocial && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AuthorSocialLinks, { author })
1274
- ] })
1275
- ] });
1276
- }
1277
- var SOCIAL_ICONS = {
1278
- Website: import_lucide_react6.Globe,
1279
- Facebook: import_lucide_react6.Facebook,
1280
- Twitter: import_lucide_react6.Twitter,
1281
- X: import_lucide_react6.Twitter,
1282
- LinkedIn: import_lucide_react6.Linkedin,
1283
- Instagram: import_lucide_react6.Instagram,
1284
- YouTube: import_lucide_react6.Youtube,
1285
- GitHub: import_lucide_react6.Github,
1286
- Newsletter: import_lucide_react6.Mail
1287
- };
1288
- function getSocialIcon(label) {
1289
- var _a;
1290
- return (_a = SOCIAL_ICONS[label]) != null ? _a : import_lucide_react6.MessageCircle;
1291
- }
1292
- function AuthorSocialLinks({ author }) {
1293
- const links = getAuthorSocialLinks(author);
1294
- if (links.length === 0) return null;
1295
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "mt-3 flex items-center gap-2", children: links.map(({ href, label }) => {
1296
- const Icon = getSocialIcon(label);
1297
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1298
- import_link9.default,
1299
- {
1300
- href,
1301
- className: "inline-flex h-8 w-8 items-center justify-center rounded-md border border-border text-muted-foreground hover:bg-accent hover:text-accent-foreground",
1302
- "aria-label": `${author.name} on ${label}`,
1303
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { className: "h-4 w-4" })
1304
- },
1305
- `${label}-${href}`
1306
- );
1307
- }) });
1308
- }
1503
+ // src/AuthorArticlesPage.tsx
1504
+ var import_link11 = __toESM(require("next/link"), 1);
1309
1505
 
1310
1506
  // src/AuthorDetailHero.tsx
1311
1507
  var import_image7 = __toESM(require("next/image"), 1);
1312
- var import_jsx_runtime15 = require("react/jsx-runtime");
1508
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1313
1509
  function getInitials2(name) {
1314
1510
  return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
1315
1511
  }
1316
1512
  function AuthorDetailHero({ author, articleCount }) {
1317
1513
  const avatar = getAuthorAvatar(author);
1318
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("section", { className: "bg-muted/40 py-16", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "mx-auto flex max-w-4xl flex-col items-center gap-6 px-4 text-center sm:px-6 lg:px-8", children: [
1319
- avatar ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1514
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("section", { className: "bg-muted/40 py-16", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "mx-auto flex max-w-4xl flex-col items-center gap-6 px-4 text-center sm:px-6 lg:px-8", children: [
1515
+ avatar ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1320
1516
  import_image7.default,
1321
1517
  {
1322
1518
  src: avatar,
@@ -1326,27 +1522,303 @@ function AuthorDetailHero({ author, articleCount }) {
1326
1522
  className: "h-28 w-28 rounded-full object-cover",
1327
1523
  priority: true
1328
1524
  }
1329
- ) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex h-28 w-28 items-center justify-center rounded-full bg-card text-3xl font-semibold text-muted-foreground shadow-sm", children: getInitials2(author.name) }),
1330
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
1331
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "mb-3 text-sm font-semibold uppercase tracking-widest text-muted-foreground", children: "Author" }),
1332
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h1", { className: "text-4xl font-bold text-foreground md:text-5xl", children: author.name }),
1333
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "mx-auto mt-4 max-w-2xl text-lg text-muted-foreground", children: author.bio }),
1334
- typeof articleCount === "number" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("p", { className: "mt-3 text-sm text-muted-foreground", children: [
1525
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex h-28 w-28 items-center justify-center rounded-full bg-card text-3xl font-semibold text-muted-foreground shadow-sm", children: getInitials2(author.name) }),
1526
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
1527
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mb-3 text-sm font-semibold uppercase tracking-widest text-muted-foreground", children: "Author" }),
1528
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h1", { className: "text-4xl font-bold text-foreground md:text-5xl", children: author.name }),
1529
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mx-auto mt-4 max-w-2xl text-lg text-muted-foreground", children: author.bio }),
1530
+ typeof articleCount === "number" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "mt-3 text-sm text-muted-foreground", children: [
1335
1531
  articleCount,
1336
1532
  " article",
1337
1533
  articleCount === 1 ? "" : "s"
1338
1534
  ] })
1339
1535
  ] }),
1340
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AuthorSocialLinks, { author })
1536
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AuthorSocialLinks, { author })
1341
1537
  ] }) });
1342
1538
  }
1343
1539
 
1344
- // src/ArticleSocialShare.tsx
1540
+ // src/eventTracking.tsx
1345
1541
  var import_react5 = require("react");
1346
- var import_lucide_react7 = require("lucide-react");
1347
- var import_jsx_runtime16 = require("react/jsx-runtime");
1348
- function ArticleSocialShare({ title, url, excerpt, shareMessage }) {
1349
- const [copied, setCopied] = (0, import_react5.useState)(false);
1542
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1543
+ var AVERAGE_WORDS_PER_MINUTE = 200;
1544
+ var DEFAULT_MEANINGFUL_READ_MS = 15e3;
1545
+ function ArticleViewTracker({ article, config }) {
1546
+ const firedMeaningfulRead = (0, import_react5.useRef)(false);
1547
+ (0, import_react5.useEffect)(() => {
1548
+ emitArticleEvent(config == null ? void 0 : config.onEvent, {
1549
+ name: "article_viewed",
1550
+ articleSlug: article.slug,
1551
+ category: article.category,
1552
+ seriesSlug: article.seriesSlug
1553
+ });
1554
+ const estimatedMs = article.wordCount ? article.wordCount / AVERAGE_WORDS_PER_MINUTE * 6e4 * 0.5 : DEFAULT_MEANINGFUL_READ_MS;
1555
+ const timer = setTimeout(() => {
1556
+ if (firedMeaningfulRead.current) return;
1557
+ firedMeaningfulRead.current = true;
1558
+ emitArticleEvent(config == null ? void 0 : config.onEvent, { name: "meaningful_read", articleSlug: article.slug });
1559
+ }, estimatedMs);
1560
+ return () => clearTimeout(timer);
1561
+ }, [article.slug]);
1562
+ return null;
1563
+ }
1564
+ function CtaViewTracker({ ctaId, articleSlug, config, children }) {
1565
+ const ref = (0, import_react5.useRef)(null);
1566
+ const fired = (0, import_react5.useRef)(false);
1567
+ (0, import_react5.useEffect)(() => {
1568
+ const node = ref.current;
1569
+ if (!node) return;
1570
+ function fire() {
1571
+ if (fired.current) return;
1572
+ fired.current = true;
1573
+ emitArticleEvent(config == null ? void 0 : config.onEvent, { name: "cta_viewed", ctaId, articleSlug });
1574
+ }
1575
+ if (typeof IntersectionObserver === "undefined") {
1576
+ fire();
1577
+ return;
1578
+ }
1579
+ const observer = new IntersectionObserver(
1580
+ (entries) => {
1581
+ if (entries.some((entry) => entry.isIntersecting)) fire();
1582
+ },
1583
+ { threshold: 0.5 }
1584
+ );
1585
+ observer.observe(node);
1586
+ return () => observer.disconnect();
1587
+ }, [ctaId, articleSlug]);
1588
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { ref, children });
1589
+ }
1590
+
1591
+ // src/AuthorArticlesPage.tsx
1592
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1593
+ function getPersonSchema(author, config, articleCount) {
1594
+ var _a, _b;
1595
+ return __spreadProps(__spreadValues(__spreadValues({
1596
+ "@context": "https://schema.org",
1597
+ "@type": "Person",
1598
+ name: author.name,
1599
+ description: author.bio,
1600
+ url: (_a = author.url) != null ? _a : getAuthorUrl(author)
1601
+ }, getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }), {
1602
+ knowsAbout: config.siteName,
1603
+ mainEntityOfPage: (_b = author.url) != null ? _b : `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`,
1604
+ interactionStatistic: {
1605
+ "@type": "InteractionCounter",
1606
+ interactionType: "https://schema.org/WriteAction",
1607
+ userInteractionCount: articleCount
1608
+ }
1609
+ });
1610
+ }
1611
+ function renderHeroSection(author, articleCount) {
1612
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(AuthorDetailHero, { author, articleCount }, "hero");
1613
+ }
1614
+ function renderPromiseSection(author) {
1615
+ return author.promise ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("section", { "aria-label": "Author promise", className: "bg-background py-12", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "mx-auto max-w-3xl px-4 text-center sm:px-6 lg:px-8", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-xl font-medium text-foreground md:text-2xl", children: author.promise }) }) }, "promise") : null;
1616
+ }
1617
+ function renderServesWhoSection(author) {
1618
+ return author.servesWho && author.servesWho.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("section", { "aria-label": "Who this author serves", className: "bg-muted/40 py-12", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "mx-auto max-w-3xl px-4 sm:px-6 lg:px-8", children: [
1619
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: "mb-4 text-center text-2xl font-bold text-foreground", children: "Who I help" }),
1620
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("ul", { className: "grid gap-3 sm:grid-cols-2", children: author.servesWho.map((who) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1621
+ "li",
1622
+ {
1623
+ className: "rounded-lg border border-border bg-card px-4 py-3 text-sm text-card-foreground",
1624
+ children: who
1625
+ },
1626
+ who
1627
+ )) })
1628
+ ] }) }, "servesWho") : null;
1629
+ }
1630
+ function renderOriginStorySection(author) {
1631
+ return author.originStory && author.originStory.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("section", { "aria-label": "Author origin story", className: "bg-background py-12", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "mx-auto max-w-3xl px-4 sm:px-6 lg:px-8", children: [
1632
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: "mb-6 text-2xl font-bold text-foreground", children: "My story" }),
1633
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "space-y-6", children: author.originStory.map((block, blockIndex) => {
1634
+ var _a;
1635
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
1636
+ block.heading && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { className: "mb-2 text-lg font-semibold text-foreground", children: block.heading }),
1637
+ block.paragraphs.map((paragraph, paragraphIndex) => {
1638
+ var _a2;
1639
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1640
+ "p",
1641
+ {
1642
+ className: "mb-3 text-muted-foreground",
1643
+ children: paragraph
1644
+ },
1645
+ `${(_a2 = block.heading) != null ? _a2 : blockIndex}-${paragraph.slice(0, 40)}-${paragraphIndex}`
1646
+ );
1647
+ })
1648
+ ] }, (_a = block.heading) != null ? _a : blockIndex);
1649
+ }) })
1650
+ ] }) }, "originStory") : null;
1651
+ }
1652
+ function renderPrinciplesSection(author) {
1653
+ return author.principles && author.principles.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("section", { "aria-label": "Author principles", className: "bg-muted/40 py-12", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "mx-auto max-w-3xl px-4 sm:px-6 lg:px-8", children: [
1654
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: "mb-4 text-center text-2xl font-bold text-foreground", children: "What I believe" }),
1655
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("ul", { className: "space-y-3", children: author.principles.map((principle) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1656
+ "li",
1657
+ {
1658
+ className: "rounded-lg border border-border bg-card px-4 py-3 text-sm text-card-foreground",
1659
+ children: principle
1660
+ },
1661
+ principle
1662
+ )) })
1663
+ ] }) }, "principles") : null;
1664
+ }
1665
+ function renderProofSection(author) {
1666
+ return author.proof && author.proof.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("section", { "aria-label": "Author proof", className: "bg-background py-12", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "mx-auto max-w-4xl px-4 sm:px-6 lg:px-8", children: [
1667
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: "mb-6 text-center text-2xl font-bold text-foreground", children: "Proof" }),
1668
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "grid gap-4 sm:grid-cols-2", children: author.proof.map((item) => {
1669
+ var _a, _b, _c;
1670
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1671
+ "div",
1672
+ {
1673
+ className: "rounded-lg border border-border bg-card p-4 text-card-foreground",
1674
+ children: [
1675
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "font-medium", children: item.claim }),
1676
+ (item.source || item.url) && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "mt-1 text-sm text-muted-foreground", children: item.url ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_link11.default, { href: item.url, className: "hover:text-primary hover:underline", children: (_c = item.source) != null ? _c : item.url }) : item.source })
1677
+ ]
1678
+ },
1679
+ `${item.claim}-${(_b = (_a = item.url) != null ? _a : item.source) != null ? _b : ""}`
1680
+ );
1681
+ }) })
1682
+ ] }) }, "proof") : null;
1683
+ }
1684
+ function renderCtaSection(author, config) {
1685
+ return author.primaryCta ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(CtaViewTracker, { ctaId: `author-cta:${author.slug}`, config, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("section", { "aria-label": "Author call to action", className: "bg-muted/40 py-12", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "mx-auto max-w-2xl px-4 text-center sm:px-6 lg:px-8", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1686
+ import_link11.default,
1687
+ {
1688
+ href: author.primaryCta.href,
1689
+ onClick: () => emitArticleEvent(config == null ? void 0 : config.onEvent, {
1690
+ name: "cta_clicked",
1691
+ ctaId: `author-cta:${author.slug}`
1692
+ }),
1693
+ className: "inline-flex items-center justify-center rounded-md bg-primary px-6 py-3 text-sm font-semibold text-primary-foreground hover:bg-primary/90",
1694
+ children: author.primaryCta.label
1695
+ }
1696
+ ) }) }) }, "cta") : null;
1697
+ }
1698
+ function renderArticlesSection(author, articles, pageSize, pagination) {
1699
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("section", { "aria-label": "Articles by this author", className: "bg-background py-16", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", children: [
1700
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "mb-8 flex items-center justify-between gap-4", children: [
1701
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_link11.default, { href: "/articles", className: "text-sm text-primary hover:underline", children: "All Articles" }),
1702
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
1703
+ "Articles by ",
1704
+ author.name
1705
+ ] })
1706
+ ] }),
1707
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(LatestArticles, { articles, pageSize, pagination })
1708
+ ] }) }, "articles");
1709
+ }
1710
+ function renderAuthorSection(section, ctx) {
1711
+ const { author, articleCount, articles, pageSize, pagination, customSection, config } = ctx;
1712
+ switch (section) {
1713
+ case "hero":
1714
+ return renderHeroSection(author, articleCount);
1715
+ case "promise":
1716
+ return renderPromiseSection(author);
1717
+ case "servesWho":
1718
+ return renderServesWhoSection(author);
1719
+ case "originStory":
1720
+ return renderOriginStorySection(author);
1721
+ case "principles":
1722
+ return renderPrinciplesSection(author);
1723
+ case "proof":
1724
+ return renderProofSection(author);
1725
+ case "cta":
1726
+ return renderCtaSection(author, config);
1727
+ case "articles":
1728
+ return renderArticlesSection(author, articles, pageSize, pagination);
1729
+ case "custom":
1730
+ return customSection != null ? customSection : null;
1731
+ default:
1732
+ return null;
1733
+ }
1734
+ }
1735
+ function AuthorArticlesPage({
1736
+ author,
1737
+ articles,
1738
+ config,
1739
+ page,
1740
+ totalPages,
1741
+ totalCount,
1742
+ sections,
1743
+ customSection
1744
+ }) {
1745
+ var _a, _b;
1746
+ const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1747
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1748
+ const articleCount = totalCount != null ? totalCount : articles.length;
1749
+ const pagination = config.listingPagination === "pages" && page !== void 0 && totalPages !== void 0 ? { page, totalPages, basePath: `/articles/authors/${author.slug}` } : void 0;
1750
+ const schemas = /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
1751
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1752
+ "script",
1753
+ {
1754
+ type: "application/ld+json",
1755
+ dangerouslySetInnerHTML: {
1756
+ __html: JSON.stringify(getPersonSchema(author, config, articleCount))
1757
+ }
1758
+ }
1759
+ ),
1760
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1761
+ CollectionPageSchema,
1762
+ {
1763
+ title: `${author.name} | ${config.siteName}`,
1764
+ description: `Articles by ${author.name} on ${config.siteName}.`,
1765
+ url: (_b = author.url) != null ? _b : `${siteUrl}/articles/authors/${author.slug}`,
1766
+ articleCount,
1767
+ items: articles.slice(0, pageSize).map((article, index) => ({
1768
+ position: index + 1,
1769
+ url: `${siteUrl}/articles/${article.slug}`,
1770
+ name: article.title
1771
+ }))
1772
+ }
1773
+ )
1774
+ ] });
1775
+ if (!sections) {
1776
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
1777
+ schemas,
1778
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("section", { className: "bg-background py-16", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", children: [
1779
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "mb-8 flex items-center justify-between gap-4", children: [
1780
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_link11.default, { href: "/articles", className: "text-sm text-primary hover:underline", children: "All Articles" }),
1781
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
1782
+ "Articles by ",
1783
+ author.name
1784
+ ] })
1785
+ ] }),
1786
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(LatestArticles, { articles, pageSize, pagination })
1787
+ ] }) })
1788
+ ] });
1789
+ }
1790
+ const sectionCtx = {
1791
+ author,
1792
+ articleCount,
1793
+ articles,
1794
+ pageSize,
1795
+ pagination,
1796
+ customSection,
1797
+ config
1798
+ };
1799
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
1800
+ schemas,
1801
+ sections.map((section) => renderAuthorSection(section, sectionCtx))
1802
+ ] });
1803
+ }
1804
+
1805
+ // src/ArticleSocialShare.tsx
1806
+ var import_react6 = require("react");
1807
+ var import_lucide_react8 = require("lucide-react");
1808
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1809
+ function ArticleSocialShare({
1810
+ title,
1811
+ url,
1812
+ excerpt,
1813
+ shareMessage,
1814
+ config,
1815
+ articleSlug
1816
+ }) {
1817
+ const [copied, setCopied] = (0, import_react6.useState)(false);
1818
+ function trackShare(channel) {
1819
+ if (!articleSlug) return;
1820
+ emitArticleEvent(config == null ? void 0 : config.onEvent, { name: "shared", articleSlug, channel });
1821
+ }
1350
1822
  const encodedTitle = encodeURIComponent(title);
1351
1823
  const encodedUrl = encodeURIComponent(url);
1352
1824
  const encodedExcerpt = encodeURIComponent(excerpt || "");
@@ -1363,102 +1835,203 @@ function ArticleSocialShare({ title, url, excerpt, shareMessage }) {
1363
1835
  try {
1364
1836
  yield navigator.clipboard.writeText(url);
1365
1837
  setCopied(true);
1838
+ trackShare("copy-link");
1366
1839
  setTimeout(() => setCopied(false), 2e3);
1367
1840
  } catch (e) {
1368
1841
  }
1369
1842
  });
1370
- const openShareWindow = (shareUrl) => {
1843
+ const openShareWindow = (shareUrl, channel) => {
1844
+ trackShare(channel);
1371
1845
  globalThis.open(shareUrl, "_blank", "width=600,height=400,scrollbars=yes,resizable=yes");
1372
1846
  };
1373
1847
  const btnClass = "inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-md border border-border bg-background text-foreground hover:bg-primary/10 hover:border-primary hover:text-primary transition-colors cursor-pointer";
1374
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "border-t border-border pt-8 mt-8", children: [
1375
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2 mb-4", children: [
1376
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.Share2, { className: "h-5 w-5 text-muted-foreground" }),
1377
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h3", { className: "text-lg font-semibold text-foreground", children: "Share this article" })
1848
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "border-t border-border pt-8 mt-8", children: [
1849
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2 mb-4", children: [
1850
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react8.Share2, { className: "h-5 w-5 text-muted-foreground" }),
1851
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { className: "text-lg font-semibold text-foreground", children: "Share this article" })
1378
1852
  ] }),
1379
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap gap-2", children: [
1380
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.linkedin), children: [
1381
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.Users, { className: "h-4 w-4" }),
1382
- "LinkedIn"
1383
- ] }),
1384
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.facebook), children: [
1385
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-xs font-bold", children: "f" }),
1386
- " Facebook"
1387
- ] }),
1388
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.twitter), children: [
1389
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.MessageCircle, { className: "h-4 w-4" }),
1390
- "Twitter"
1391
- ] }),
1392
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.reddit), children: [
1393
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.FileText, { className: "h-4 w-4" }),
1394
- "Reddit"
1395
- ] }),
1396
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.whatsapp), children: [
1397
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.MessageCircle, { className: "h-4 w-4" }),
1398
- "WhatsApp"
1399
- ] }),
1400
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.telegram), children: [
1401
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.MessageCircle, { className: "h-4 w-4" }),
1402
- "Telegram"
1403
- ] }),
1404
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("a", { className: btnClass, href: shareLinks.email, children: [
1405
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.Mail, { className: "h-4 w-4" }),
1853
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-wrap gap-2", children: [
1854
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1855
+ "button",
1856
+ {
1857
+ type: "button",
1858
+ className: btnClass,
1859
+ onClick: () => openShareWindow(shareLinks.linkedin, "linkedin"),
1860
+ children: [
1861
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react8.Users, { className: "h-4 w-4" }),
1862
+ "LinkedIn"
1863
+ ]
1864
+ }
1865
+ ),
1866
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1867
+ "button",
1868
+ {
1869
+ type: "button",
1870
+ className: btnClass,
1871
+ onClick: () => openShareWindow(shareLinks.facebook, "facebook"),
1872
+ children: [
1873
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-xs font-bold", children: "f" }),
1874
+ " Facebook"
1875
+ ]
1876
+ }
1877
+ ),
1878
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1879
+ "button",
1880
+ {
1881
+ type: "button",
1882
+ className: btnClass,
1883
+ onClick: () => openShareWindow(shareLinks.twitter, "twitter"),
1884
+ children: [
1885
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react8.MessageCircle, { className: "h-4 w-4" }),
1886
+ "Twitter"
1887
+ ]
1888
+ }
1889
+ ),
1890
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1891
+ "button",
1892
+ {
1893
+ type: "button",
1894
+ className: btnClass,
1895
+ onClick: () => openShareWindow(shareLinks.reddit, "reddit"),
1896
+ children: [
1897
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react8.FileText, { className: "h-4 w-4" }),
1898
+ "Reddit"
1899
+ ]
1900
+ }
1901
+ ),
1902
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1903
+ "button",
1904
+ {
1905
+ type: "button",
1906
+ className: btnClass,
1907
+ onClick: () => openShareWindow(shareLinks.whatsapp, "whatsapp"),
1908
+ children: [
1909
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react8.MessageCircle, { className: "h-4 w-4" }),
1910
+ "WhatsApp"
1911
+ ]
1912
+ }
1913
+ ),
1914
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1915
+ "button",
1916
+ {
1917
+ type: "button",
1918
+ className: btnClass,
1919
+ onClick: () => openShareWindow(shareLinks.telegram, "telegram"),
1920
+ children: [
1921
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react8.MessageCircle, { className: "h-4 w-4" }),
1922
+ "Telegram"
1923
+ ]
1924
+ }
1925
+ ),
1926
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("a", { className: btnClass, href: shareLinks.email, onClick: () => trackShare("email"), children: [
1927
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react8.Mail, { className: "h-4 w-4" }),
1406
1928
  "Email"
1407
1929
  ] }),
1408
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: copyToClipboard, children: [
1409
- copied ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.Check, { className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.Copy, { className: "h-4 w-4" }),
1930
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("button", { type: "button", className: btnClass, onClick: copyToClipboard, children: [
1931
+ copied ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react8.Check, { className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react8.Copy, { className: "h-4 w-4" }),
1410
1932
  copied ? "Copied!" : "Copy Link"
1411
1933
  ] })
1412
1934
  ] }),
1413
- shareMessage && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-sm text-muted-foreground mt-3", children: shareMessage })
1935
+ shareMessage && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-sm text-muted-foreground mt-3", children: shareMessage })
1414
1936
  ] });
1415
1937
  }
1416
1938
 
1417
1939
  // src/ArticleNavigation.tsx
1418
- var import_link10 = __toESM(require("next/link"), 1);
1419
- var import_lucide_react8 = require("lucide-react");
1420
- var import_jsx_runtime17 = require("react/jsx-runtime");
1940
+ var import_link12 = __toESM(require("next/link"), 1);
1941
+ var import_lucide_react9 = require("lucide-react");
1942
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1421
1943
  function truncateTitle(title, maxLength = 60) {
1422
1944
  return title.length > maxLength ? `${title.substring(0, maxLength)}...` : title;
1423
1945
  }
1424
- function ArticleNavigation({ previous, next, basePath }) {
1946
+ function ArticleNavigation({
1947
+ previous,
1948
+ next,
1949
+ basePath,
1950
+ fromSlug,
1951
+ pathKey,
1952
+ config
1953
+ }) {
1425
1954
  if (!previous && !next) return null;
1426
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("nav", { className: "mt-12 pt-8 border-t border-border", "aria-label": "Article navigation", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex justify-between gap-4", children: [
1427
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex-1 min-w-0", children: previous && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1428
- import_link10.default,
1955
+ function emitStep(toSlug2, direction) {
1956
+ if (!pathKey || !fromSlug) return;
1957
+ emitArticleEvent(config == null ? void 0 : config.onEvent, {
1958
+ name: "path_step_advanced",
1959
+ pathKey,
1960
+ fromSlug,
1961
+ toSlug: toSlug2,
1962
+ direction
1963
+ });
1964
+ }
1965
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("nav", { className: "mt-12 pt-8 border-t border-border", "aria-label": "Article navigation", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex justify-between gap-4", children: [
1966
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex-1 min-w-0", children: previous && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1967
+ import_link12.default,
1429
1968
  {
1430
1969
  href: `${basePath}/${previous.slug}`,
1970
+ onClick: () => emitStep(previous.slug, "previous"),
1431
1971
  className: "group flex items-center gap-3 p-4 rounded-lg border border-border hover:border-primary/20 hover:bg-muted/50 transition-colors",
1432
1972
  children: [
1433
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react8.ChevronLeft, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" }),
1434
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "min-w-0 flex-1", children: [
1435
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-sm text-muted-foreground mb-1", children: "Previous Article" }),
1436
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "font-medium text-foreground truncate", title: previous.title, children: truncateTitle(previous.title) })
1973
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.ChevronLeft, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" }),
1974
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "min-w-0 flex-1", children: [
1975
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-sm text-muted-foreground mb-1", children: "Previous Article" }),
1976
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "font-medium text-foreground truncate", title: previous.title, children: truncateTitle(previous.title) })
1437
1977
  ] })
1438
1978
  ]
1439
1979
  }
1440
1980
  ) }),
1441
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex-1 min-w-0", children: next && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1442
- import_link10.default,
1981
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex-1 min-w-0", children: next && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1982
+ import_link12.default,
1443
1983
  {
1444
1984
  href: `${basePath}/${next.slug}`,
1985
+ onClick: () => emitStep(next.slug, "next"),
1445
1986
  className: "group flex items-center justify-end gap-3 p-4 rounded-lg border border-border hover:border-primary/20 hover:bg-muted/50 transition-colors",
1446
1987
  children: [
1447
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "min-w-0 flex-1 text-right", children: [
1448
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-sm text-muted-foreground mb-1", children: "Next Article" }),
1449
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "font-medium text-foreground truncate", title: next.title, children: truncateTitle(next.title) })
1988
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "min-w-0 flex-1 text-right", children: [
1989
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-sm text-muted-foreground mb-1", children: "Next Article" }),
1990
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "font-medium text-foreground truncate", title: next.title, children: truncateTitle(next.title) })
1450
1991
  ] }),
1451
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react8.ChevronRight, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" })
1992
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.ChevronRight, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" })
1452
1993
  ]
1453
1994
  }
1454
1995
  ) })
1455
1996
  ] }) });
1456
1997
  }
1457
1998
 
1999
+ // src/RelatedArticlesSection.tsx
2000
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2001
+ function RelatedArticlesSection({
2002
+ articles,
2003
+ category,
2004
+ heading,
2005
+ config,
2006
+ fromSlug,
2007
+ source = "category"
2008
+ }) {
2009
+ if (articles.length === 0) return null;
2010
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("section", { className: "mt-12 pt-8 border-t border-border", "aria-label": "Related articles", children: [
2011
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h2", { className: "text-xl font-semibold text-foreground mb-6", children: heading != null ? heading : `More in ${category}` }),
2012
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "grid gap-6 sm:grid-cols-2 lg:grid-cols-3", children: articles.map((article) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2013
+ "div",
2014
+ {
2015
+ onClickCapture: () => {
2016
+ if (!fromSlug) return;
2017
+ emitArticleEvent(config == null ? void 0 : config.onEvent, {
2018
+ name: "related_article_clicked",
2019
+ fromSlug,
2020
+ toSlug: article.slug,
2021
+ source
2022
+ });
2023
+ },
2024
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ArticleCard, { article, config })
2025
+ },
2026
+ article.slug
2027
+ )) })
2028
+ ] });
2029
+ }
2030
+
1458
2031
  // src/ArticleBackLink.tsx
1459
- var import_link11 = __toESM(require("next/link"), 1);
1460
- var import_lucide_react9 = require("lucide-react");
1461
- var import_jsx_runtime18 = require("react/jsx-runtime");
2032
+ var import_link13 = __toESM(require("next/link"), 1);
2033
+ var import_lucide_react10 = require("lucide-react");
2034
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1462
2035
  function ArticleBackLink({
1463
2036
  config,
1464
2037
  href = "/articles",
@@ -1466,13 +2039,13 @@ function ArticleBackLink({
1466
2039
  className
1467
2040
  }) {
1468
2041
  if (config.showBackToArticles === false) return null;
1469
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1470
- import_link11.default,
2042
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
2043
+ import_link13.default,
1471
2044
  {
1472
2045
  href,
1473
2046
  className: className != null ? className : "inline-flex items-center gap-2 mb-6 -ml-3 px-3 py-2 rounded-md text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors",
1474
2047
  children: [
1475
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react9.ArrowLeft, { className: "h-4 w-4" }),
2048
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.ArrowLeft, { className: "h-4 w-4" }),
1476
2049
  label
1477
2050
  ]
1478
2051
  }
@@ -1480,17 +2053,17 @@ function ArticleBackLink({
1480
2053
  }
1481
2054
 
1482
2055
  // src/ArticleTOC.tsx
1483
- var import_jsx_runtime19 = require("react/jsx-runtime");
2056
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1484
2057
  function ArticleTOC({ toc, className }) {
1485
2058
  if (!toc.length) return null;
1486
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
2059
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1487
2060
  "nav",
1488
2061
  {
1489
2062
  "aria-label": "Table of contents",
1490
2063
  className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
1491
2064
  children: [
1492
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
1493
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ul", { className: "space-y-1 text-sm", children: toc.map((item) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("li", { style: { paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2065
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
2066
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("ul", { className: "space-y-1 text-sm", children: toc.map((item) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("li", { style: { paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1494
2067
  "a",
1495
2068
  {
1496
2069
  href: `#${item.id}`,
@@ -1504,35 +2077,35 @@ function ArticleTOC({ toc, className }) {
1504
2077
  }
1505
2078
 
1506
2079
  // src/ScrollToTop.tsx
1507
- var import_react6 = require("react");
1508
- var import_lucide_react10 = require("lucide-react");
1509
- var import_jsx_runtime20 = require("react/jsx-runtime");
2080
+ var import_react7 = require("react");
2081
+ var import_lucide_react11 = require("lucide-react");
2082
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1510
2083
  function ScrollToTop() {
1511
- const [visible, setVisible] = (0, import_react6.useState)(false);
1512
- (0, import_react6.useEffect)(() => {
2084
+ const [visible, setVisible] = (0, import_react7.useState)(false);
2085
+ (0, import_react7.useEffect)(() => {
1513
2086
  const onScroll = () => setVisible(globalThis.scrollY > 300);
1514
2087
  globalThis.addEventListener("scroll", onScroll, { passive: true });
1515
2088
  return () => globalThis.removeEventListener("scroll", onScroll);
1516
2089
  }, []);
1517
2090
  if (!visible) return null;
1518
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2091
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1519
2092
  "button",
1520
2093
  {
1521
2094
  "aria-label": "Scroll to top",
1522
2095
  onClick: () => globalThis.scrollTo({ top: 0, behavior: "smooth" }),
1523
2096
  className: "fixed bottom-8 right-8 z-50 rounded-full bg-primary p-2 shadow-md text-primary-foreground hover:bg-primary/90 transition-colors",
1524
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react10.ArrowUp, { className: "h-5 w-5" })
2097
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react11.ArrowUp, { className: "h-5 w-5" })
1525
2098
  }
1526
2099
  );
1527
2100
  }
1528
2101
 
1529
2102
  // src/CommentsSection.tsx
1530
- var import_react9 = require("next-auth/react");
1531
- var import_react10 = require("react");
2103
+ var import_react10 = require("next-auth/react");
2104
+ var import_react11 = require("react");
1532
2105
 
1533
2106
  // src/CommentForm.tsx
1534
- var import_react7 = require("react");
1535
- var import_jsx_runtime21 = require("react/jsx-runtime");
2107
+ var import_react8 = require("react");
2108
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1536
2109
  var MAX_LENGTH = 2e3;
1537
2110
  var WARN_THRESHOLD = 1800;
1538
2111
  function submitLabel(submitting, parentId) {
@@ -1540,9 +2113,9 @@ function submitLabel(submitting, parentId) {
1540
2113
  return parentId ? "Reply" : "Post comment";
1541
2114
  }
1542
2115
  function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1543
- const [body, setBody] = (0, import_react7.useState)("");
1544
- const [submitting, setSubmitting] = (0, import_react7.useState)(false);
1545
- const [error, setError] = (0, import_react7.useState)(null);
2116
+ const [body, setBody] = (0, import_react8.useState)("");
2117
+ const [submitting, setSubmitting] = (0, import_react8.useState)(false);
2118
+ const [error, setError] = (0, import_react8.useState)(null);
1546
2119
  const remaining = MAX_LENGTH - body.length;
1547
2120
  function handleSubmit(e) {
1548
2121
  return __async(this, null, function* () {
@@ -1571,8 +2144,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1571
2144
  }
1572
2145
  });
1573
2146
  }
1574
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
1575
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2147
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
2148
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1576
2149
  "textarea",
1577
2150
  {
1578
2151
  value: body,
@@ -1585,13 +2158,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1585
2158
  "aria-label": parentId ? "Reply text" : "Comment text"
1586
2159
  }
1587
2160
  ),
1588
- remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
2161
+ remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
1589
2162
  remaining,
1590
2163
  " characters remaining"
1591
2164
  ] }),
1592
- error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-xs text-destructive", children: error }),
1593
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex gap-2", children: [
1594
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2165
+ error && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "text-xs text-destructive", children: error }),
2166
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex gap-2", children: [
2167
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1595
2168
  "button",
1596
2169
  {
1597
2170
  type: "submit",
@@ -1600,7 +2173,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1600
2173
  children: submitLabel(submitting, parentId)
1601
2174
  }
1602
2175
  ),
1603
- onCancel && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2176
+ onCancel && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1604
2177
  "button",
1605
2178
  {
1606
2179
  type: "button",
@@ -1614,8 +2187,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1614
2187
  }
1615
2188
 
1616
2189
  // src/CommentItem.tsx
1617
- var import_react8 = require("react");
1618
- var import_jsx_runtime22 = require("react/jsx-runtime");
2190
+ var import_react9 = require("react");
2191
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1619
2192
  function relativeTime(iso) {
1620
2193
  const diff = Date.now() - new Date(iso).getTime();
1621
2194
  const minutes = Math.floor(diff / 6e4);
@@ -1636,8 +2209,8 @@ function CommentItem({
1636
2209
  isReply = false,
1637
2210
  onRefresh
1638
2211
  }) {
1639
- const [showReplyForm, setShowReplyForm] = (0, import_react8.useState)(false);
1640
- const [deleting, setDeleting] = (0, import_react8.useState)(false);
2212
+ const [showReplyForm, setShowReplyForm] = (0, import_react9.useState)(false);
2213
+ const [deleting, setDeleting] = (0, import_react9.useState)(false);
1641
2214
  const replies = "replies" in comment ? comment.replies : [];
1642
2215
  const canReply = !isReply && !!currentUserId && !comment.isDeleted;
1643
2216
  const canDelete = !!currentUserId && currentUserId === comment.authorId && !comment.isDeleted;
@@ -1653,15 +2226,15 @@ function CommentItem({
1653
2226
  }
1654
2227
  });
1655
2228
  }
1656
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
1657
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
1658
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
1659
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
1660
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
2229
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
2230
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
2231
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
2232
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
2233
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
1661
2234
  ] }),
1662
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
1663
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex gap-3 pt-1", children: [
1664
- canReply && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2235
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
2236
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex gap-3 pt-1", children: [
2237
+ canReply && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1665
2238
  "button",
1666
2239
  {
1667
2240
  onClick: () => setShowReplyForm((v) => !v),
@@ -1669,7 +2242,7 @@ function CommentItem({
1669
2242
  children: showReplyForm ? "Cancel" : "Reply"
1670
2243
  }
1671
2244
  ),
1672
- canDelete && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2245
+ canDelete && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1673
2246
  "button",
1674
2247
  {
1675
2248
  onClick: handleDelete,
@@ -1680,7 +2253,7 @@ function CommentItem({
1680
2253
  )
1681
2254
  ] })
1682
2255
  ] }) }),
1683
- showReplyForm && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2256
+ showReplyForm && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1684
2257
  CommentForm,
1685
2258
  {
1686
2259
  articleSlug,
@@ -1692,7 +2265,7 @@ function CommentItem({
1692
2265
  onCancel: () => setShowReplyForm(false)
1693
2266
  }
1694
2267
  ) }),
1695
- replies.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2268
+ replies.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1696
2269
  CommentItem,
1697
2270
  {
1698
2271
  comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
@@ -1707,7 +2280,7 @@ function CommentItem({
1707
2280
  }
1708
2281
 
1709
2282
  // src/CommentThread.tsx
1710
- var import_jsx_runtime23 = require("react/jsx-runtime");
2283
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1711
2284
  function CommentThread({
1712
2285
  comments,
1713
2286
  articleSlug,
@@ -1715,9 +2288,9 @@ function CommentThread({
1715
2288
  onRefresh
1716
2289
  }) {
1717
2290
  if (comments.length === 0) {
1718
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
2291
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
1719
2292
  }
1720
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2293
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1721
2294
  CommentItem,
1722
2295
  {
1723
2296
  comment,
@@ -1730,17 +2303,17 @@ function CommentThread({
1730
2303
  }
1731
2304
 
1732
2305
  // src/CommentsSection.tsx
1733
- var import_jsx_runtime24 = require("react/jsx-runtime");
2306
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1734
2307
  function CommentsSection({ articleSlug, config }) {
1735
2308
  var _a, _b, _c, _d, _e, _f;
1736
- const { data: session, status } = (0, import_react9.useSession)();
1737
- const [comments, setComments] = (0, import_react10.useState)([]);
1738
- const [loading, setLoading] = (0, import_react10.useState)(true);
1739
- const [fetchError, setFetchError] = (0, import_react10.useState)(null);
2309
+ const { data: session, status } = (0, import_react10.useSession)();
2310
+ const [comments, setComments] = (0, import_react11.useState)([]);
2311
+ const [loading, setLoading] = (0, import_react11.useState)(true);
2312
+ const [fetchError, setFetchError] = (0, import_react11.useState)(null);
1740
2313
  const perArticleEnabled = (_b = (_a = config.perArticleOverride) == null ? void 0 : _a[articleSlug]) != null ? _b : true;
1741
2314
  const enabled = config.enabled && perArticleEnabled;
1742
2315
  const currentUserId = (_f = (_e = (_c = session == null ? void 0 : session.user) == null ? void 0 : _c.email) != null ? _e : (_d = session == null ? void 0 : session.user) == null ? void 0 : _d.name) != null ? _f : void 0;
1743
- const fetchComments = (0, import_react10.useCallback)(() => __async(this, null, function* () {
2316
+ const fetchComments = (0, import_react11.useCallback)(() => __async(this, null, function* () {
1744
2317
  setLoading(true);
1745
2318
  setFetchError(null);
1746
2319
  try {
@@ -1754,7 +2327,7 @@ function CommentsSection({ articleSlug, config }) {
1754
2327
  setLoading(false);
1755
2328
  }
1756
2329
  }), [articleSlug]);
1757
- (0, import_react10.useEffect)(() => {
2330
+ (0, import_react11.useEffect)(() => {
1758
2331
  if (enabled) {
1759
2332
  fetchComments().catch(() => void 0);
1760
2333
  }
@@ -1762,13 +2335,13 @@ function CommentsSection({ articleSlug, config }) {
1762
2335
  if (!enabled) return null;
1763
2336
  const count = comments.length;
1764
2337
  const label = count === 1 ? "1 comment" : `${count} comments`;
1765
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
1766
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
1767
- status === "authenticated" ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
1768
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2338
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
2339
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
2340
+ status === "authenticated" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
2341
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1769
2342
  "button",
1770
2343
  {
1771
- onClick: () => (0, import_react9.signIn)(),
2344
+ onClick: () => (0, import_react10.signIn)(),
1772
2345
  className: "text-primary underline hover:no-underline focus:outline-none",
1773
2346
  children: "Sign in"
1774
2347
  }
@@ -1776,8 +2349,8 @@ function CommentsSection({ articleSlug, config }) {
1776
2349
  " ",
1777
2350
  "to join the discussion."
1778
2351
  ] }),
1779
- fetchError && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-sm text-destructive", children: fetchError }),
1780
- loading ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2352
+ fetchError && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: "text-sm text-destructive", children: fetchError }),
2353
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1781
2354
  CommentThread,
1782
2355
  {
1783
2356
  comments,
@@ -1796,10 +2369,10 @@ function CommentsSection({ articleSlug, config }) {
1796
2369
  ArticleDetailHero,
1797
2370
  ArticleNavigation,
1798
2371
  ArticleSEO,
1799
- ArticleSchema,
1800
2372
  ArticleSearchBar,
1801
2373
  ArticleSocialShare,
1802
2374
  ArticleTOC,
2375
+ ArticleViewTracker,
1803
2376
  ArticlesHero,
1804
2377
  ArticlesPage,
1805
2378
  AuthorArticlesPage,
@@ -1814,6 +2387,7 @@ function CommentsSection({ articleSlug, config }) {
1814
2387
  CommentItem,
1815
2388
  CommentThread,
1816
2389
  CommentsSection,
2390
+ CtaViewTracker,
1817
2391
  DEFAULT_CATEGORIES_PAGE_SIZE,
1818
2392
  DEFAULT_LAYOUT,
1819
2393
  DEFAULT_PAGE_SIZE,
@@ -1821,7 +2395,10 @@ function CommentsSection({ articleSlug, config }) {
1821
2395
  FeaturedArticle,
1822
2396
  LatestArticles,
1823
2397
  LatestArticlesSection,
2398
+ PaginationNav,
2399
+ RelatedArticlesSection,
1824
2400
  ScrollToTop,
2401
+ SeriesArticlesPage,
1825
2402
  breadcrumbsAreEnabled,
1826
2403
  getBreadcrumbsConfig,
1827
2404
  useArticles