@fullstackdatasolutions/articles 0.12.0 → 1.1.0

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