@fullstackdatasolutions/articles 0.11.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 (71) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +568 -6
  3. package/dist/index.cjs +970 -389
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +375 -21
  6. package/dist/index.d.ts +375 -21
  7. package/dist/index.js +954 -377
  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 +65 -7
  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 +148 -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__/useArticles.test.ts +28 -0
  59. package/src/__tests__/validateArticles.test.ts +312 -0
  60. package/src/articleTypes.ts +109 -0
  61. package/src/articlesConfig.ts +37 -1
  62. package/src/eventTracking.tsx +97 -0
  63. package/src/events.ts +105 -0
  64. package/src/index.ts +26 -1
  65. package/src/markdown.ts +41 -0
  66. package/src/pagination.ts +93 -0
  67. package/src/seoUtils.ts +198 -11
  68. package/src/server-articles.ts +199 -6
  69. package/src/server.ts +46 -2
  70. package/src/useArticles.ts +10 -5
  71. 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
 
@@ -715,12 +921,12 @@ function getBreadcrumbsConfig(config) {
715
921
 
716
922
  // src/useArticles.ts
717
923
  var import_react3 = require("react");
718
- function useArticles() {
924
+ function useArticles(initialArticles, initialCategories) {
719
925
  const [searchQuery, setSearchQuery] = (0, import_react3.useState)("");
720
926
  const debounceRef = (0, import_react3.useRef)(null);
721
- const [articles, setArticles] = (0, import_react3.useState)([]);
722
- const [categories, setCategories] = (0, import_react3.useState)([]);
723
- const [loading, setLoading] = (0, import_react3.useState)(true);
927
+ const [articles, setArticles] = (0, import_react3.useState)(initialArticles != null ? initialArticles : []);
928
+ const [categories, setCategories] = (0, import_react3.useState)(initialCategories != null ? initialCategories : []);
929
+ const [loading, setLoading] = (0, import_react3.useState)(initialArticles === void 0);
724
930
  const [error, setError] = (0, import_react3.useState)(null);
725
931
  const fetchArticles = (0, import_react3.useCallback)((query) => __async(this, null, function* () {
726
932
  try {
@@ -758,7 +964,7 @@ function useArticles() {
758
964
  }
759
965
  }), []);
760
966
  (0, import_react3.useEffect)(() => {
761
- fetchArticles("");
967
+ if (initialArticles === void 0) fetchArticles("");
762
968
  }, [fetchArticles]);
763
969
  const handleSearch = (0, import_react3.useCallback)(
764
970
  (query) => {
@@ -782,7 +988,7 @@ function useArticles() {
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,
@@ -889,26 +1097,48 @@ function renderSection(section, ctx, config) {
889
1097
  return null;
890
1098
  }
891
1099
  }
892
- function ArticlesPage({ config }) {
1100
+ function ArticlesPage({
1101
+ config,
1102
+ initialArticles,
1103
+ initialCategories,
1104
+ page,
1105
+ totalPages,
1106
+ totalCount
1107
+ }) {
893
1108
  var _a, _b, _c;
894
- const state = useArticles();
1109
+ const state = useArticles(initialArticles, initialCategories);
895
1110
  const layout = (_a = config.layout) != null ? _a : DEFAULT_LAYOUT;
896
1111
  const pageSize = (_b = config.pageSize) != null ? _b : DEFAULT_PAGE_SIZE;
897
1112
  const categoriesPageSize = (_c = config.categoriesPageSize) != null ? _c : DEFAULT_CATEGORIES_PAGE_SIZE;
898
1113
  const themeVars = buildThemeVars(config.theme);
899
- 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");
900
1118
  const articlesWithoutFeatured = hasFeatured ? state.articles.slice(1) : state.articles;
901
1119
  const displayedArticles = state.searchQuery ? state.articles : articlesWithoutFeatured;
902
- const ctx = __spreadProps(__spreadValues({}, state), { displayedArticles, pageSize, categoriesPageSize });
903
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: themeVars, children: [
904
- layout.map((section) => renderSection(section, ctx, config)),
905
- 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)(
906
1131
  CollectionPageSchema,
907
1132
  {
908
1133
  title: `Articles | ${config.siteName}`,
909
- description: `Browse all ${state.articles.length} articles on ${config.siteName}.`,
1134
+ description: `Browse all ${articleCount} articles on ${config.siteName}.`,
910
1135
  url: `${config.siteUrl}/articles`,
911
- 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
+ }))
912
1142
  }
913
1143
  )
914
1144
  ] });
@@ -916,10 +1146,10 @@ function ArticlesPage({ config }) {
916
1146
 
917
1147
  // src/ArticleDetailHero.tsx
918
1148
  var import_react4 = require("react");
919
- var import_image4 = __toESM(require("next/image"), 1);
920
- var import_link6 = __toESM(require("next/link"), 1);
921
- var import_lucide_react5 = require("lucide-react");
922
- 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");
923
1153
  function toSlug(cat) {
924
1154
  return cat.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
925
1155
  }
@@ -932,7 +1162,7 @@ function ArticleDetailHero({
932
1162
  }) {
933
1163
  const showConfiguredAuthors = showAuthor && authors.length > 0;
934
1164
  const showLegacyAuthor = showAuthor && authors.length === 0 && article.author.trim().length > 0;
935
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1165
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
936
1166
  "section",
937
1167
  {
938
1168
  style: {
@@ -944,8 +1174,8 @@ function ArticleDetailHero({
944
1174
  overflow: "hidden"
945
1175
  },
946
1176
  children: [
947
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
948
- import_image4.default,
1177
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1178
+ import_image5.default,
949
1179
  {
950
1180
  src: article.featuredImage,
951
1181
  alt: article.title,
@@ -956,7 +1186,7 @@ function ArticleDetailHero({
956
1186
  priority: true
957
1187
  }
958
1188
  ),
959
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1189
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
960
1190
  "div",
961
1191
  {
962
1192
  style: {
@@ -966,7 +1196,7 @@ function ArticleDetailHero({
966
1196
  }
967
1197
  }
968
1198
  ),
969
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1199
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
970
1200
  "div",
971
1201
  {
972
1202
  style: {
@@ -980,7 +1210,7 @@ function ArticleDetailHero({
980
1210
  width: "100%"
981
1211
  },
982
1212
  children: [
983
- 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)(
984
1214
  "div",
985
1215
  {
986
1216
  style: {
@@ -991,15 +1221,15 @@ function ArticleDetailHero({
991
1221
  marginBottom: "1rem"
992
1222
  },
993
1223
  children: article.categories.slice(0, 4).map(
994
- (cat) => categoryBasePath ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
995
- import_link6.default,
1224
+ (cat) => categoryBasePath ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1225
+ import_link8.default,
996
1226
  {
997
1227
  href: `${categoryBasePath}/${toSlug(cat)}`,
998
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",
999
1229
  children: cat
1000
1230
  },
1001
1231
  cat
1002
- ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1232
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1003
1233
  "span",
1004
1234
  {
1005
1235
  className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full",
@@ -1010,8 +1240,8 @@ function ArticleDetailHero({
1010
1240
  )
1011
1241
  }
1012
1242
  ),
1013
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h1", { className: "text-4xl md:text-5xl font-bold mb-4", children: article.title }),
1014
- /* @__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)(
1015
1245
  "div",
1016
1246
  {
1017
1247
  style: {
@@ -1023,23 +1253,23 @@ function ArticleDetailHero({
1023
1253
  color: "rgba(255,255,255,0.8)"
1024
1254
  },
1025
1255
  children: [
1026
- showDate && article.date && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1027
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react5.Calendar, { className: "h-4 w-4" }),
1028
- /* @__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 })
1029
1259
  ] }),
1030
- /* @__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.Clock, { className: "h-4 w-4" }),
1032
- /* @__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 })
1033
1263
  ] }),
1034
- showConfiguredAuthors && /* @__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.User, { className: "h-4 w-4" }),
1036
- /* @__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: [
1037
1267
  "By",
1038
1268
  " ",
1039
- 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: [
1040
1270
  index > 0 && ", ",
1041
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1042
- import_link6.default,
1271
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1272
+ import_link8.default,
1043
1273
  {
1044
1274
  href: `/articles/authors/${author.slug}`,
1045
1275
  className: "font-medium text-white underline-offset-4 hover:underline",
@@ -1049,15 +1279,26 @@ function ArticleDetailHero({
1049
1279
  ] }, author.slug))
1050
1280
  ] })
1051
1281
  ] }),
1052
- showLegacyAuthor && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1053
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react5.User, { className: "h-4 w-4" }),
1054
- /* @__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: [
1055
1285
  "By ",
1056
1286
  article.author
1057
1287
  ] })
1058
1288
  ] })
1059
1289
  ]
1060
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
+ }
1061
1302
  )
1062
1303
  ]
1063
1304
  }
@@ -1068,9 +1309,9 @@ function ArticleDetailHero({
1068
1309
  }
1069
1310
 
1070
1311
  // src/CategoryArticlesPage.tsx
1071
- var import_image5 = __toESM(require("next/image"), 1);
1072
- var import_link7 = __toESM(require("next/link"), 1);
1073
- 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");
1074
1315
  function getCategoryDescription(config, slug, categoryName) {
1075
1316
  var _a;
1076
1317
  const raw = (_a = config.categoryDescriptions) == null ? void 0 : _a[slug];
@@ -1112,7 +1353,14 @@ function buildCategoryBreadcrumbEntry(entry, context) {
1112
1353
  }
1113
1354
  return [{ name: context.categoryName }];
1114
1355
  }
1115
- function CategoryArticlesPage({ category, articles, config }) {
1356
+ function CategoryArticlesPage({
1357
+ category,
1358
+ articles,
1359
+ config,
1360
+ page,
1361
+ totalPages,
1362
+ totalCount
1363
+ }) {
1116
1364
  var _a;
1117
1365
  if (articles.length === 0) return null;
1118
1366
  const categoryName = articles[0].category;
@@ -1121,8 +1369,25 @@ function CategoryArticlesPage({ category, articles, config }) {
1121
1369
  const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1122
1370
  const breadcrumbConfig = getBreadcrumbsConfig(config);
1123
1371
  const breadcrumbItems = buildCategoryBreadcrumbItems(config, categoryName);
1124
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
1125
- 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)(
1126
1391
  Breadcrumb,
1127
1392
  {
1128
1393
  items: breadcrumbItems,
@@ -1130,7 +1395,7 @@ function CategoryArticlesPage({ category, articles, config }) {
1130
1395
  showSchema: breadcrumbConfig.showSchema !== false
1131
1396
  }
1132
1397
  ),
1133
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1398
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1134
1399
  "section",
1135
1400
  {
1136
1401
  style: {
@@ -1142,8 +1407,8 @@ function CategoryArticlesPage({ category, articles, config }) {
1142
1407
  overflow: "hidden"
1143
1408
  },
1144
1409
  children: [
1145
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1146
- import_image5.default,
1410
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1411
+ import_image6.default,
1147
1412
  {
1148
1413
  src: heroImage,
1149
1414
  alt: categoryName,
@@ -1153,8 +1418,8 @@ function CategoryArticlesPage({ category, articles, config }) {
1153
1418
  priority: true
1154
1419
  }
1155
1420
  ),
1156
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.6)" } }),
1157
- /* @__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)(
1158
1423
  "div",
1159
1424
  {
1160
1425
  style: {
@@ -1165,10 +1430,10 @@ function CategoryArticlesPage({ category, articles, config }) {
1165
1430
  padding: "0 1rem"
1166
1431
  },
1167
1432
  children: [
1168
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
1169
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
1170
- description.long && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
1171
- /* @__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: [
1172
1437
  articles.length,
1173
1438
  " article",
1174
1439
  articles.length === 1 ? "" : "s"
@@ -1179,140 +1444,75 @@ function CategoryArticlesPage({ category, articles, config }) {
1179
1444
  ]
1180
1445
  }
1181
1446
  ),
1182
- /* @__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: [
1183
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mb-8 flex items-center justify-between", children: [
1184
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_link7.default, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
1185
- /* @__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 })
1186
1451
  ] }),
1187
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(LatestArticles, { articles, pageSize })
1452
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(LatestArticles, { articles, pageSize, pagination })
1188
1453
  ] }) })
1189
1454
  ] });
1190
1455
  }
1191
1456
 
1192
- // src/AuthorArticlesPage.tsx
1193
- var import_link8 = __toESM(require("next/link"), 1);
1194
- var import_jsx_runtime13 = require("react/jsx-runtime");
1195
- function getPersonSchema(author, config, articleCount) {
1196
- var _a, _b;
1197
- return __spreadProps(__spreadValues(__spreadValues({
1198
- "@context": "https://schema.org",
1199
- "@type": "Person",
1200
- name: author.name,
1201
- description: author.bio,
1202
- url: (_a = author.url) != null ? _a : getAuthorUrl(author)
1203
- }, getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }), {
1204
- knowsAbout: config.siteName,
1205
- mainEntityOfPage: (_b = author.url) != null ? _b : `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`,
1206
- interactionStatistic: {
1207
- "@type": "InteractionCounter",
1208
- interactionType: "https://schema.org/WriteAction",
1209
- userInteractionCount: articleCount
1210
- }
1211
- });
1212
- }
1213
- 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 }) {
1214
1461
  var _a;
1215
- const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1216
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
1217
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1218
- "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,
1219
1469
  {
1220
- type: "application/ld+json",
1221
- dangerouslySetInnerHTML: {
1222
- __html: JSON.stringify(getPersonSchema(author, config, articles.length))
1223
- }
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
+ }))
1224
1479
  }
1225
1480
  ),
1226
- /* @__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: [
1227
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "mb-8 flex items-center justify-between gap-4", children: [
1228
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_link8.default, { href: "/articles", className: "text-sm text-primary hover:underline", children: "All Articles" }),
1229
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
1230
- "Articles by ",
1231
- 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"
1232
1489
  ] })
1233
1490
  ] }),
1234
- /* @__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
+ )
1235
1499
  ] }) })
1236
1500
  ] });
1237
1501
  }
1238
1502
 
1239
- // src/AuthorCard.tsx
1240
- var import_image6 = __toESM(require("next/image"), 1);
1241
- var import_link9 = __toESM(require("next/link"), 1);
1242
- var import_lucide_react6 = require("lucide-react");
1243
- var import_jsx_runtime14 = require("react/jsx-runtime");
1244
- function getInitials(name) {
1245
- return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
1246
- }
1247
- function AuthorCard({
1248
- author,
1249
- linkToPage = true,
1250
- showBio = false,
1251
- showSocial = false
1252
- }) {
1253
- const avatar = getAuthorAvatar(author);
1254
- 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 });
1255
- 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: [
1256
- avatar ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1257
- import_image6.default,
1258
- {
1259
- src: avatar,
1260
- alt: author.name,
1261
- width: 48,
1262
- height: 48,
1263
- className: "h-12 w-12 rounded-full object-cover"
1264
- }
1265
- ) : /* @__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) }),
1266
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "min-w-0 flex-1", children: [
1267
- name,
1268
- showBio && author.bio && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "mt-1 line-clamp-2 text-sm text-muted-foreground", children: author.bio }),
1269
- showSocial && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AuthorSocialLinks, { author })
1270
- ] })
1271
- ] });
1272
- }
1273
- var SOCIAL_ICONS = {
1274
- Website: import_lucide_react6.Globe,
1275
- Facebook: import_lucide_react6.Facebook,
1276
- Twitter: import_lucide_react6.Twitter,
1277
- X: import_lucide_react6.Twitter,
1278
- LinkedIn: import_lucide_react6.Linkedin,
1279
- Instagram: import_lucide_react6.Instagram,
1280
- YouTube: import_lucide_react6.Youtube,
1281
- GitHub: import_lucide_react6.Github,
1282
- Newsletter: import_lucide_react6.Mail
1283
- };
1284
- function getSocialIcon(label) {
1285
- var _a;
1286
- return (_a = SOCIAL_ICONS[label]) != null ? _a : import_lucide_react6.MessageCircle;
1287
- }
1288
- function AuthorSocialLinks({ author }) {
1289
- const links = getAuthorSocialLinks(author);
1290
- if (links.length === 0) return null;
1291
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "mt-3 flex items-center gap-2", children: links.map(({ href, label }) => {
1292
- const Icon = getSocialIcon(label);
1293
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1294
- import_link9.default,
1295
- {
1296
- href,
1297
- 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",
1298
- "aria-label": `${author.name} on ${label}`,
1299
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { className: "h-4 w-4" })
1300
- },
1301
- `${label}-${href}`
1302
- );
1303
- }) });
1304
- }
1503
+ // src/AuthorArticlesPage.tsx
1504
+ var import_link11 = __toESM(require("next/link"), 1);
1305
1505
 
1306
1506
  // src/AuthorDetailHero.tsx
1307
1507
  var import_image7 = __toESM(require("next/image"), 1);
1308
- var import_jsx_runtime15 = require("react/jsx-runtime");
1508
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1309
1509
  function getInitials2(name) {
1310
1510
  return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
1311
1511
  }
1312
1512
  function AuthorDetailHero({ author, articleCount }) {
1313
1513
  const avatar = getAuthorAvatar(author);
1314
- 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: [
1315
- 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)(
1316
1516
  import_image7.default,
1317
1517
  {
1318
1518
  src: avatar,
@@ -1322,27 +1522,303 @@ function AuthorDetailHero({ author, articleCount }) {
1322
1522
  className: "h-28 w-28 rounded-full object-cover",
1323
1523
  priority: true
1324
1524
  }
1325
- ) : /* @__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) }),
1326
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
1327
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "mb-3 text-sm font-semibold uppercase tracking-widest text-muted-foreground", children: "Author" }),
1328
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h1", { className: "text-4xl font-bold text-foreground md:text-5xl", children: author.name }),
1329
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "mx-auto mt-4 max-w-2xl text-lg text-muted-foreground", children: author.bio }),
1330
- 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: [
1331
1531
  articleCount,
1332
1532
  " article",
1333
1533
  articleCount === 1 ? "" : "s"
1334
1534
  ] })
1335
1535
  ] }),
1336
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AuthorSocialLinks, { author })
1536
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AuthorSocialLinks, { author })
1337
1537
  ] }) });
1338
1538
  }
1339
1539
 
1340
- // src/ArticleSocialShare.tsx
1540
+ // src/eventTracking.tsx
1341
1541
  var import_react5 = require("react");
1342
- var import_lucide_react7 = require("lucide-react");
1343
- var import_jsx_runtime16 = require("react/jsx-runtime");
1344
- function ArticleSocialShare({ title, url, excerpt, shareMessage }) {
1345
- 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
+ }
1346
1822
  const encodedTitle = encodeURIComponent(title);
1347
1823
  const encodedUrl = encodeURIComponent(url);
1348
1824
  const encodedExcerpt = encodeURIComponent(excerpt || "");
@@ -1359,102 +1835,203 @@ function ArticleSocialShare({ title, url, excerpt, shareMessage }) {
1359
1835
  try {
1360
1836
  yield navigator.clipboard.writeText(url);
1361
1837
  setCopied(true);
1838
+ trackShare("copy-link");
1362
1839
  setTimeout(() => setCopied(false), 2e3);
1363
1840
  } catch (e) {
1364
1841
  }
1365
1842
  });
1366
- const openShareWindow = (shareUrl) => {
1843
+ const openShareWindow = (shareUrl, channel) => {
1844
+ trackShare(channel);
1367
1845
  globalThis.open(shareUrl, "_blank", "width=600,height=400,scrollbars=yes,resizable=yes");
1368
1846
  };
1369
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";
1370
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "border-t border-border pt-8 mt-8", children: [
1371
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2 mb-4", children: [
1372
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.Share2, { className: "h-5 w-5 text-muted-foreground" }),
1373
- /* @__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" })
1374
1852
  ] }),
1375
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap gap-2", children: [
1376
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.linkedin), children: [
1377
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.Users, { className: "h-4 w-4" }),
1378
- "LinkedIn"
1379
- ] }),
1380
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.facebook), children: [
1381
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-xs font-bold", children: "f" }),
1382
- " Facebook"
1383
- ] }),
1384
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.twitter), children: [
1385
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.MessageCircle, { className: "h-4 w-4" }),
1386
- "Twitter"
1387
- ] }),
1388
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.reddit), children: [
1389
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.FileText, { className: "h-4 w-4" }),
1390
- "Reddit"
1391
- ] }),
1392
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.whatsapp), children: [
1393
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.MessageCircle, { className: "h-4 w-4" }),
1394
- "WhatsApp"
1395
- ] }),
1396
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.telegram), children: [
1397
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.MessageCircle, { className: "h-4 w-4" }),
1398
- "Telegram"
1399
- ] }),
1400
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("a", { className: btnClass, href: shareLinks.email, children: [
1401
- /* @__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" }),
1402
1928
  "Email"
1403
1929
  ] }),
1404
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { className: btnClass, onClick: copyToClipboard, children: [
1405
- 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" }),
1406
1932
  copied ? "Copied!" : "Copy Link"
1407
1933
  ] })
1408
1934
  ] }),
1409
- 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 })
1410
1936
  ] });
1411
1937
  }
1412
1938
 
1413
1939
  // src/ArticleNavigation.tsx
1414
- var import_link10 = __toESM(require("next/link"), 1);
1415
- var import_lucide_react8 = require("lucide-react");
1416
- 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");
1417
1943
  function truncateTitle(title, maxLength = 60) {
1418
1944
  return title.length > maxLength ? `${title.substring(0, maxLength)}...` : title;
1419
1945
  }
1420
- function ArticleNavigation({ previous, next, basePath }) {
1946
+ function ArticleNavigation({
1947
+ previous,
1948
+ next,
1949
+ basePath,
1950
+ fromSlug,
1951
+ pathKey,
1952
+ config
1953
+ }) {
1421
1954
  if (!previous && !next) return null;
1422
- 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: [
1423
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex-1 min-w-0", children: previous && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1424
- 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,
1425
1968
  {
1426
1969
  href: `${basePath}/${previous.slug}`,
1970
+ onClick: () => emitStep(previous.slug, "previous"),
1427
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",
1428
1972
  children: [
1429
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react8.ChevronLeft, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" }),
1430
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "min-w-0 flex-1", children: [
1431
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-sm text-muted-foreground mb-1", children: "Previous Article" }),
1432
- /* @__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) })
1433
1977
  ] })
1434
1978
  ]
1435
1979
  }
1436
1980
  ) }),
1437
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex-1 min-w-0", children: next && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1438
- 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,
1439
1983
  {
1440
1984
  href: `${basePath}/${next.slug}`,
1985
+ onClick: () => emitStep(next.slug, "next"),
1441
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",
1442
1987
  children: [
1443
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "min-w-0 flex-1 text-right", children: [
1444
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-sm text-muted-foreground mb-1", children: "Next Article" }),
1445
- /* @__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) })
1446
1991
  ] }),
1447
- /* @__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" })
1448
1993
  ]
1449
1994
  }
1450
1995
  ) })
1451
1996
  ] }) });
1452
1997
  }
1453
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
+
1454
2031
  // src/ArticleBackLink.tsx
1455
- var import_link11 = __toESM(require("next/link"), 1);
1456
- var import_lucide_react9 = require("lucide-react");
1457
- 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");
1458
2035
  function ArticleBackLink({
1459
2036
  config,
1460
2037
  href = "/articles",
@@ -1462,13 +2039,13 @@ function ArticleBackLink({
1462
2039
  className
1463
2040
  }) {
1464
2041
  if (config.showBackToArticles === false) return null;
1465
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1466
- import_link11.default,
2042
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
2043
+ import_link13.default,
1467
2044
  {
1468
2045
  href,
1469
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",
1470
2047
  children: [
1471
- /* @__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" }),
1472
2049
  label
1473
2050
  ]
1474
2051
  }
@@ -1476,17 +2053,17 @@ function ArticleBackLink({
1476
2053
  }
1477
2054
 
1478
2055
  // src/ArticleTOC.tsx
1479
- var import_jsx_runtime19 = require("react/jsx-runtime");
2056
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1480
2057
  function ArticleTOC({ toc, className }) {
1481
2058
  if (!toc.length) return null;
1482
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
2059
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1483
2060
  "nav",
1484
2061
  {
1485
2062
  "aria-label": "Table of contents",
1486
2063
  className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
1487
2064
  children: [
1488
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
1489
- /* @__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)(
1490
2067
  "a",
1491
2068
  {
1492
2069
  href: `#${item.id}`,
@@ -1500,35 +2077,35 @@ function ArticleTOC({ toc, className }) {
1500
2077
  }
1501
2078
 
1502
2079
  // src/ScrollToTop.tsx
1503
- var import_react6 = require("react");
1504
- var import_lucide_react10 = require("lucide-react");
1505
- 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");
1506
2083
  function ScrollToTop() {
1507
- const [visible, setVisible] = (0, import_react6.useState)(false);
1508
- (0, import_react6.useEffect)(() => {
2084
+ const [visible, setVisible] = (0, import_react7.useState)(false);
2085
+ (0, import_react7.useEffect)(() => {
1509
2086
  const onScroll = () => setVisible(globalThis.scrollY > 300);
1510
2087
  globalThis.addEventListener("scroll", onScroll, { passive: true });
1511
2088
  return () => globalThis.removeEventListener("scroll", onScroll);
1512
2089
  }, []);
1513
2090
  if (!visible) return null;
1514
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2091
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1515
2092
  "button",
1516
2093
  {
1517
2094
  "aria-label": "Scroll to top",
1518
2095
  onClick: () => globalThis.scrollTo({ top: 0, behavior: "smooth" }),
1519
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",
1520
- 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" })
1521
2098
  }
1522
2099
  );
1523
2100
  }
1524
2101
 
1525
2102
  // src/CommentsSection.tsx
1526
- var import_react9 = require("next-auth/react");
1527
- var import_react10 = require("react");
2103
+ var import_react10 = require("next-auth/react");
2104
+ var import_react11 = require("react");
1528
2105
 
1529
2106
  // src/CommentForm.tsx
1530
- var import_react7 = require("react");
1531
- var import_jsx_runtime21 = require("react/jsx-runtime");
2107
+ var import_react8 = require("react");
2108
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1532
2109
  var MAX_LENGTH = 2e3;
1533
2110
  var WARN_THRESHOLD = 1800;
1534
2111
  function submitLabel(submitting, parentId) {
@@ -1536,9 +2113,9 @@ function submitLabel(submitting, parentId) {
1536
2113
  return parentId ? "Reply" : "Post comment";
1537
2114
  }
1538
2115
  function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1539
- const [body, setBody] = (0, import_react7.useState)("");
1540
- const [submitting, setSubmitting] = (0, import_react7.useState)(false);
1541
- 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);
1542
2119
  const remaining = MAX_LENGTH - body.length;
1543
2120
  function handleSubmit(e) {
1544
2121
  return __async(this, null, function* () {
@@ -1567,8 +2144,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1567
2144
  }
1568
2145
  });
1569
2146
  }
1570
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
1571
- /* @__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)(
1572
2149
  "textarea",
1573
2150
  {
1574
2151
  value: body,
@@ -1581,13 +2158,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1581
2158
  "aria-label": parentId ? "Reply text" : "Comment text"
1582
2159
  }
1583
2160
  ),
1584
- 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: [
1585
2162
  remaining,
1586
2163
  " characters remaining"
1587
2164
  ] }),
1588
- error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-xs text-destructive", children: error }),
1589
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex gap-2", children: [
1590
- /* @__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)(
1591
2168
  "button",
1592
2169
  {
1593
2170
  type: "submit",
@@ -1596,7 +2173,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1596
2173
  children: submitLabel(submitting, parentId)
1597
2174
  }
1598
2175
  ),
1599
- onCancel && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2176
+ onCancel && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1600
2177
  "button",
1601
2178
  {
1602
2179
  type: "button",
@@ -1610,8 +2187,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1610
2187
  }
1611
2188
 
1612
2189
  // src/CommentItem.tsx
1613
- var import_react8 = require("react");
1614
- var import_jsx_runtime22 = require("react/jsx-runtime");
2190
+ var import_react9 = require("react");
2191
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1615
2192
  function relativeTime(iso) {
1616
2193
  const diff = Date.now() - new Date(iso).getTime();
1617
2194
  const minutes = Math.floor(diff / 6e4);
@@ -1632,8 +2209,8 @@ function CommentItem({
1632
2209
  isReply = false,
1633
2210
  onRefresh
1634
2211
  }) {
1635
- const [showReplyForm, setShowReplyForm] = (0, import_react8.useState)(false);
1636
- 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);
1637
2214
  const replies = "replies" in comment ? comment.replies : [];
1638
2215
  const canReply = !isReply && !!currentUserId && !comment.isDeleted;
1639
2216
  const canDelete = !!currentUserId && currentUserId === comment.authorId && !comment.isDeleted;
@@ -1649,15 +2226,15 @@ function CommentItem({
1649
2226
  }
1650
2227
  });
1651
2228
  }
1652
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
1653
- /* @__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: [
1654
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
1655
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
1656
- /* @__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) })
1657
2234
  ] }),
1658
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
1659
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex gap-3 pt-1", children: [
1660
- 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)(
1661
2238
  "button",
1662
2239
  {
1663
2240
  onClick: () => setShowReplyForm((v) => !v),
@@ -1665,7 +2242,7 @@ function CommentItem({
1665
2242
  children: showReplyForm ? "Cancel" : "Reply"
1666
2243
  }
1667
2244
  ),
1668
- canDelete && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2245
+ canDelete && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1669
2246
  "button",
1670
2247
  {
1671
2248
  onClick: handleDelete,
@@ -1676,7 +2253,7 @@ function CommentItem({
1676
2253
  )
1677
2254
  ] })
1678
2255
  ] }) }),
1679
- 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)(
1680
2257
  CommentForm,
1681
2258
  {
1682
2259
  articleSlug,
@@ -1688,7 +2265,7 @@ function CommentItem({
1688
2265
  onCancel: () => setShowReplyForm(false)
1689
2266
  }
1690
2267
  ) }),
1691
- 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)(
1692
2269
  CommentItem,
1693
2270
  {
1694
2271
  comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
@@ -1703,7 +2280,7 @@ function CommentItem({
1703
2280
  }
1704
2281
 
1705
2282
  // src/CommentThread.tsx
1706
- var import_jsx_runtime23 = require("react/jsx-runtime");
2283
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1707
2284
  function CommentThread({
1708
2285
  comments,
1709
2286
  articleSlug,
@@ -1711,9 +2288,9 @@ function CommentThread({
1711
2288
  onRefresh
1712
2289
  }) {
1713
2290
  if (comments.length === 0) {
1714
- 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." });
1715
2292
  }
1716
- 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)(
1717
2294
  CommentItem,
1718
2295
  {
1719
2296
  comment,
@@ -1726,17 +2303,17 @@ function CommentThread({
1726
2303
  }
1727
2304
 
1728
2305
  // src/CommentsSection.tsx
1729
- var import_jsx_runtime24 = require("react/jsx-runtime");
2306
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1730
2307
  function CommentsSection({ articleSlug, config }) {
1731
2308
  var _a, _b, _c, _d, _e, _f;
1732
- const { data: session, status } = (0, import_react9.useSession)();
1733
- const [comments, setComments] = (0, import_react10.useState)([]);
1734
- const [loading, setLoading] = (0, import_react10.useState)(true);
1735
- 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);
1736
2313
  const perArticleEnabled = (_b = (_a = config.perArticleOverride) == null ? void 0 : _a[articleSlug]) != null ? _b : true;
1737
2314
  const enabled = config.enabled && perArticleEnabled;
1738
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;
1739
- const fetchComments = (0, import_react10.useCallback)(() => __async(this, null, function* () {
2316
+ const fetchComments = (0, import_react11.useCallback)(() => __async(this, null, function* () {
1740
2317
  setLoading(true);
1741
2318
  setFetchError(null);
1742
2319
  try {
@@ -1750,7 +2327,7 @@ function CommentsSection({ articleSlug, config }) {
1750
2327
  setLoading(false);
1751
2328
  }
1752
2329
  }), [articleSlug]);
1753
- (0, import_react10.useEffect)(() => {
2330
+ (0, import_react11.useEffect)(() => {
1754
2331
  if (enabled) {
1755
2332
  fetchComments().catch(() => void 0);
1756
2333
  }
@@ -1758,13 +2335,13 @@ function CommentsSection({ articleSlug, config }) {
1758
2335
  if (!enabled) return null;
1759
2336
  const count = comments.length;
1760
2337
  const label = count === 1 ? "1 comment" : `${count} comments`;
1761
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
1762
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
1763
- 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: [
1764
- /* @__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)(
1765
2342
  "button",
1766
2343
  {
1767
- onClick: () => (0, import_react9.signIn)(),
2344
+ onClick: () => (0, import_react10.signIn)(),
1768
2345
  className: "text-primary underline hover:no-underline focus:outline-none",
1769
2346
  children: "Sign in"
1770
2347
  }
@@ -1772,8 +2349,8 @@ function CommentsSection({ articleSlug, config }) {
1772
2349
  " ",
1773
2350
  "to join the discussion."
1774
2351
  ] }),
1775
- fetchError && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-sm text-destructive", children: fetchError }),
1776
- 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)(
1777
2354
  CommentThread,
1778
2355
  {
1779
2356
  comments,
@@ -1792,10 +2369,10 @@ function CommentsSection({ articleSlug, config }) {
1792
2369
  ArticleDetailHero,
1793
2370
  ArticleNavigation,
1794
2371
  ArticleSEO,
1795
- ArticleSchema,
1796
2372
  ArticleSearchBar,
1797
2373
  ArticleSocialShare,
1798
2374
  ArticleTOC,
2375
+ ArticleViewTracker,
1799
2376
  ArticlesHero,
1800
2377
  ArticlesPage,
1801
2378
  AuthorArticlesPage,
@@ -1810,6 +2387,7 @@ function CommentsSection({ articleSlug, config }) {
1810
2387
  CommentItem,
1811
2388
  CommentThread,
1812
2389
  CommentsSection,
2390
+ CtaViewTracker,
1813
2391
  DEFAULT_CATEGORIES_PAGE_SIZE,
1814
2392
  DEFAULT_LAYOUT,
1815
2393
  DEFAULT_PAGE_SIZE,
@@ -1817,7 +2395,10 @@ function CommentsSection({ articleSlug, config }) {
1817
2395
  FeaturedArticle,
1818
2396
  LatestArticles,
1819
2397
  LatestArticlesSection,
2398
+ PaginationNav,
2399
+ RelatedArticlesSection,
1820
2400
  ScrollToTop,
2401
+ SeriesArticlesPage,
1821
2402
  breadcrumbsAreEnabled,
1822
2403
  getBreadcrumbsConfig,
1823
2404
  useArticles