@fullstackdatasolutions/articles 0.11.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +568 -6
  3. package/dist/index.cjs +970 -389
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +375 -21
  6. package/dist/index.d.ts +375 -21
  7. package/dist/index.js +954 -377
  8. package/dist/index.js.map +1 -1
  9. package/dist/nextjs.cjs +74 -6
  10. package/dist/nextjs.cjs.map +1 -1
  11. package/dist/nextjs.d.cts +141 -0
  12. package/dist/nextjs.d.ts +141 -0
  13. package/dist/nextjs.js +74 -6
  14. package/dist/nextjs.js.map +1 -1
  15. package/dist/server.cjs +665 -27
  16. package/dist/server.cjs.map +1 -1
  17. package/dist/server.d.cts +349 -3
  18. package/dist/server.d.ts +349 -3
  19. package/dist/server.js +643 -29
  20. package/dist/server.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/ArticleCard.tsx +37 -1
  23. package/src/ArticleContent.tsx +144 -5
  24. package/src/ArticleDetailHero.tsx +23 -0
  25. package/src/ArticleNavigation.tsx +32 -1
  26. package/src/ArticleSchemas.tsx +43 -39
  27. package/src/ArticleSocialShare.tsx +54 -10
  28. package/src/ArticlesPage.tsx +65 -7
  29. package/src/AuthorArticlesPage.tsx +308 -14
  30. package/src/AuthorCard.tsx +1 -1
  31. package/src/CategoryArticlesPage.tsx +34 -2
  32. package/src/LatestArticles.tsx +28 -1
  33. package/src/LatestArticlesSection.tsx +15 -1
  34. package/src/PaginationNav.tsx +78 -0
  35. package/src/RelatedArticlesSection.tsx +55 -0
  36. package/src/SeriesArticlesPage.tsx +66 -0
  37. package/src/__tests__/ArticleCard.test.tsx +63 -3
  38. package/src/__tests__/ArticleContent.test.tsx +143 -0
  39. package/src/__tests__/ArticleDetailHero.test.tsx +30 -0
  40. package/src/__tests__/ArticleNavigation.test.tsx +81 -3
  41. package/src/__tests__/ArticleSchemas.test.tsx +155 -81
  42. package/src/__tests__/ArticleSocialShare.test.tsx +54 -0
  43. package/src/__tests__/ArticlesPage.test.tsx +148 -0
  44. package/src/__tests__/AuthorArticlesPage.test.tsx +304 -3
  45. package/src/__tests__/CategoryArticlesPage.test.tsx +116 -1
  46. package/src/__tests__/LatestArticles.test.tsx +52 -0
  47. package/src/__tests__/LatestArticlesSection.test.tsx +28 -0
  48. package/src/__tests__/PaginationNav.test.tsx +73 -0
  49. package/src/__tests__/RelatedArticlesSection.test.tsx +132 -0
  50. package/src/__tests__/SeriesArticlesPage.test.tsx +121 -0
  51. package/src/__tests__/eventTracking.test.tsx +145 -0
  52. package/src/__tests__/events.test.ts +82 -0
  53. package/src/__tests__/markdown.test.ts +78 -1
  54. package/src/__tests__/pagination.test.ts +178 -0
  55. package/src/__tests__/seoUtils-authors.test.ts +37 -0
  56. package/src/__tests__/seoUtils.test.ts +246 -0
  57. package/src/__tests__/server-articles.test.ts +356 -1
  58. package/src/__tests__/useArticles.test.ts +28 -0
  59. package/src/__tests__/validateArticles.test.ts +312 -0
  60. package/src/articleTypes.ts +109 -0
  61. package/src/articlesConfig.ts +37 -1
  62. package/src/eventTracking.tsx +97 -0
  63. package/src/events.ts +105 -0
  64. package/src/index.ts +26 -1
  65. package/src/markdown.ts +41 -0
  66. package/src/pagination.ts +93 -0
  67. package/src/seoUtils.ts +198 -11
  68. package/src/server-articles.ts +199 -6
  69. package/src/server.ts +46 -2
  70. package/src/useArticles.ts +10 -5
  71. package/src/validateArticles.ts +260 -0
package/dist/index.js CHANGED
@@ -230,43 +230,17 @@ function BreadcrumbSchema({ items }) {
230
230
 
231
231
  // src/ArticleSchemas.tsx
232
232
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
233
- function getPersonSchemas(article, authors) {
233
+ function getPersonSchemas(article, authors, config) {
234
234
  let resolvedAuthors = [];
235
235
  if (authors == null ? void 0 : authors.length) {
236
236
  resolvedAuthors = [...authors];
237
237
  } else if (article.author) {
238
238
  resolvedAuthors = [{ name: article.author, slug: "", bio: "" }];
239
239
  }
240
- return resolvedAuthors.map((author) => __spreadValues(__spreadValues({
240
+ return resolvedAuthors.map((author) => __spreadValues(__spreadValues(__spreadValues({
241
241
  "@type": "Person",
242
242
  name: author.name
243
- }, author.url && { url: author.url }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }));
244
- }
245
- function ArticleSchema({
246
- article,
247
- articleUrl,
248
- siteName,
249
- showAuthor = true,
250
- authors
251
- }) {
252
- const personSchemas = getPersonSchemas(article, authors);
253
- const schema = __spreadProps(__spreadValues(__spreadValues({
254
- "@context": "https://schema.org",
255
- "@type": "Article",
256
- headline: article.title,
257
- description: article.excerpt,
258
- image: article.featuredImage
259
- }, article.date && { datePublished: new Date(article.date).toISOString() }), showAuthor && personSchemas.length > 0 && { author: personSchemas }), {
260
- publisher: { "@type": "Organization", name: siteName },
261
- mainEntityOfPage: { "@type": "WebPage", "@id": articleUrl }
262
- });
263
- return /* @__PURE__ */ jsx3(
264
- "script",
265
- {
266
- type: "application/ld+json",
267
- dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
268
- }
269
- );
243
+ }, author.url && { url: author.url }), getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }));
270
244
  }
271
245
  function ArticleSEO({
272
246
  article,
@@ -274,12 +248,13 @@ function ArticleSEO({
274
248
  siteName,
275
249
  siteLogo,
276
250
  showAuthor = true,
277
- authors
251
+ authors,
252
+ config
278
253
  }) {
279
254
  const publishedAt = article.date ? new Date(article.date).toISOString() : void 0;
280
255
  const modifiedAt = article.lastmod ? new Date(article.lastmod).toISOString() : publishedAt;
281
- const personSchemas = getPersonSchemas(article, authors);
282
- const structuredData = __spreadValues(__spreadProps(__spreadValues(__spreadValues(__spreadValues({
256
+ const personSchemas = getPersonSchemas(article, authors, config);
257
+ const structuredData = __spreadValues(__spreadValues(__spreadValues(__spreadProps(__spreadValues(__spreadValues(__spreadValues({
283
258
  "@context": "https://schema.org",
284
259
  "@type": article.articleType || "Article",
285
260
  mainEntityOfPage: { "@type": "WebPage", "@id": articleUrl },
@@ -290,8 +265,9 @@ function ArticleSEO({
290
265
  "@type": "Organization",
291
266
  name: siteName
292
267
  }, siteLogo && { logo: { "@type": "ImageObject", url: siteLogo } }),
293
- description: article.excerpt
294
- }), article.series && { isPartOf: { "@type": "Blog", name: article.series } });
268
+ description: article.excerpt,
269
+ articleSection: article.category
270
+ }), 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 } });
295
271
  return /* @__PURE__ */ jsxs3(Fragment2, { children: [
296
272
  /* @__PURE__ */ jsx3(
297
273
  "script",
@@ -359,16 +335,27 @@ function CollectionPageSchema({
359
335
  title,
360
336
  description,
361
337
  url,
362
- articleCount
338
+ articleCount,
339
+ items
363
340
  }) {
364
- const schema = {
341
+ const schema = __spreadValues({
365
342
  "@context": "https://schema.org",
366
343
  "@type": "CollectionPage",
367
344
  name: title,
368
345
  description,
369
346
  url,
370
347
  numberOfItems: articleCount
371
- };
348
+ }, items && items.length > 0 && {
349
+ mainEntity: {
350
+ "@type": "ItemList",
351
+ itemListElement: items.map((item) => ({
352
+ "@type": "ListItem",
353
+ position: item.position,
354
+ url: item.url,
355
+ name: item.name
356
+ }))
357
+ }
358
+ });
372
359
  return /* @__PURE__ */ jsx3(
373
360
  "script",
374
361
  {
@@ -509,14 +496,102 @@ import { Search as Search2 } from "lucide-react";
509
496
  import { useState as useState2, useEffect as useEffect2 } from "react";
510
497
 
511
498
  // src/ArticleCard.tsx
499
+ import Image4 from "next/image";
500
+ import Link6 from "next/link";
501
+ import { ArrowRight as ArrowRight2, Calendar as Calendar2, Clock as Clock2 } from "lucide-react";
502
+
503
+ // src/AuthorCard.tsx
512
504
  import Image3 from "next/image";
513
505
  import Link5 from "next/link";
514
- import { ArrowRight as ArrowRight2, Calendar as Calendar2, Clock as Clock2 } from "lucide-react";
506
+ import {
507
+ Facebook,
508
+ Github,
509
+ Globe,
510
+ Instagram,
511
+ Linkedin,
512
+ Mail,
513
+ MessageCircle,
514
+ Twitter,
515
+ Youtube
516
+ } from "lucide-react";
515
517
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
516
- function ArticleCard({ article }) {
517
- return /* @__PURE__ */ jsxs7("div", { className: "rounded-lg border border-border bg-card text-card-foreground shadow-sm hover:shadow-lg transition-shadow duration-300", children: [
518
- /* @__PURE__ */ jsx7("div", { className: "p-0", children: /* @__PURE__ */ jsx7(
518
+ function getInitials(name) {
519
+ return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
520
+ }
521
+ function AuthorCard({
522
+ author,
523
+ linkToPage = true,
524
+ showBio = false,
525
+ showSocial = false
526
+ }) {
527
+ const avatar = getAuthorAvatar(author);
528
+ const name = linkToPage ? /* @__PURE__ */ jsx7(Link5, { href: getAuthorUrl(author), className: "font-medium text-foreground hover:text-primary", children: author.name }) : /* @__PURE__ */ jsx7("span", { className: "font-medium text-foreground", children: author.name });
529
+ return /* @__PURE__ */ jsxs7("div", { className: "flex items-start gap-3 rounded-lg border border-border bg-card p-4 text-card-foreground", children: [
530
+ avatar ? /* @__PURE__ */ jsx7(
519
531
  Image3,
532
+ {
533
+ src: avatar,
534
+ alt: author.name,
535
+ width: 48,
536
+ height: 48,
537
+ className: "h-12 w-12 rounded-full object-cover"
538
+ }
539
+ ) : /* @__PURE__ */ jsx7("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) }),
540
+ /* @__PURE__ */ jsxs7("div", { className: "min-w-0 flex-1", children: [
541
+ name,
542
+ showBio && author.bio && /* @__PURE__ */ jsx7("p", { className: "mt-1 line-clamp-2 text-sm text-muted-foreground", children: author.bio }),
543
+ showSocial && /* @__PURE__ */ jsx7(AuthorSocialLinks, { author })
544
+ ] })
545
+ ] });
546
+ }
547
+ var SOCIAL_ICONS = {
548
+ Website: Globe,
549
+ Facebook,
550
+ Twitter,
551
+ X: Twitter,
552
+ LinkedIn: Linkedin,
553
+ Instagram,
554
+ YouTube: Youtube,
555
+ GitHub: Github,
556
+ Newsletter: Mail
557
+ };
558
+ function getSocialIcon(label) {
559
+ var _a;
560
+ return (_a = SOCIAL_ICONS[label]) != null ? _a : MessageCircle;
561
+ }
562
+ function AuthorSocialLinks({ author }) {
563
+ const links = getAuthorSocialLinks(author);
564
+ if (links.length === 0) return null;
565
+ return /* @__PURE__ */ jsx7("div", { className: "mt-3 flex items-center gap-2", children: links.map(({ href, label }) => {
566
+ const Icon = getSocialIcon(label);
567
+ return /* @__PURE__ */ jsx7(
568
+ Link5,
569
+ {
570
+ href,
571
+ 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",
572
+ "aria-label": `${author.name} on ${label}`,
573
+ children: /* @__PURE__ */ jsx7(Icon, { className: "h-4 w-4" })
574
+ },
575
+ `${label}-${href}`
576
+ );
577
+ }) });
578
+ }
579
+
580
+ // src/events.ts
581
+ function emitArticleEvent(handler, event) {
582
+ if (!handler) return;
583
+ try {
584
+ handler(__spreadProps(__spreadValues({}, event), { timestamp: Date.now() }));
585
+ } catch (e) {
586
+ }
587
+ }
588
+
589
+ // src/ArticleCard.tsx
590
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
591
+ function ArticleCard({ article, config }) {
592
+ return /* @__PURE__ */ jsxs8("div", { className: "rounded-lg border border-border bg-card text-card-foreground shadow-sm hover:shadow-lg transition-shadow duration-300", children: [
593
+ /* @__PURE__ */ jsx8("div", { className: "p-0", children: /* @__PURE__ */ jsx8(
594
+ Image4,
520
595
  {
521
596
  src: article.featuredImage,
522
597
  alt: article.title,
@@ -525,35 +600,67 @@ function ArticleCard({ article }) {
525
600
  height: 200
526
601
  }
527
602
  ) }),
528
- /* @__PURE__ */ jsxs7("div", { className: "p-6", children: [
529
- /* @__PURE__ */ jsx7("div", { className: "flex gap-2 mb-3", children: /* @__PURE__ */ jsx7("span", { className: "bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded", children: article.category }) }),
530
- /* @__PURE__ */ jsx7("h3", { className: "text-lg font-semibold leading-none tracking-tight mb-2", children: /* @__PURE__ */ jsx7(
531
- Link5,
603
+ /* @__PURE__ */ jsxs8("div", { className: "p-6", children: [
604
+ /* @__PURE__ */ jsx8("div", { className: "flex gap-2 mb-3", children: /* @__PURE__ */ jsx8("span", { className: "bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded", children: article.category }) }),
605
+ /* @__PURE__ */ jsx8("h3", { className: "text-lg font-semibold leading-none tracking-tight mb-2", children: /* @__PURE__ */ jsx8(
606
+ Link6,
532
607
  {
533
608
  href: `/articles/${article.slug}`,
534
609
  className: "hover:text-indigo-600 transition-colors",
535
610
  children: article.title
536
611
  }
537
612
  ) }),
538
- /* @__PURE__ */ jsx7("p", { className: "text-muted-foreground mb-4 line-clamp-3", children: article.excerpt }),
539
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between text-sm text-muted-foreground border-t pt-4", children: [
540
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3", children: [
541
- article.date && /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
542
- /* @__PURE__ */ jsx7(Calendar2, { className: "h-3 w-3" }),
543
- /* @__PURE__ */ jsx7("span", { children: article.date })
613
+ /* @__PURE__ */ jsx8("p", { className: "text-muted-foreground mb-4 line-clamp-3", children: article.excerpt }),
614
+ article.authorSlug && /* @__PURE__ */ jsxs8(
615
+ Link6,
616
+ {
617
+ href: `/articles/authors/${article.authorSlug}`,
618
+ className: "mb-4 flex items-center gap-2 text-sm text-muted-foreground hover:text-primary transition-colors",
619
+ onClick: () => emitArticleEvent(config == null ? void 0 : config.onEvent, {
620
+ name: "author_clicked",
621
+ articleSlug: article.slug,
622
+ authorSlug: article.authorSlug
623
+ }),
624
+ children: [
625
+ article.authorAvatar ? /* @__PURE__ */ jsx8(
626
+ Image4,
627
+ {
628
+ src: article.authorAvatar,
629
+ alt: "",
630
+ width: 24,
631
+ height: 24,
632
+ className: "h-6 w-6 rounded-full object-cover"
633
+ }
634
+ ) : /* @__PURE__ */ jsx8(
635
+ "span",
636
+ {
637
+ "aria-hidden": "true",
638
+ className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground",
639
+ children: getInitials(article.author)
640
+ }
641
+ ),
642
+ /* @__PURE__ */ jsx8("span", { children: article.author })
643
+ ]
644
+ }
645
+ ),
646
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between text-sm text-muted-foreground border-t pt-4", children: [
647
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-3", children: [
648
+ article.date && /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
649
+ /* @__PURE__ */ jsx8(Calendar2, { className: "h-3 w-3" }),
650
+ /* @__PURE__ */ jsx8("span", { children: article.date })
544
651
  ] }),
545
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
546
- /* @__PURE__ */ jsx7(Clock2, { className: "h-3 w-3" }),
547
- /* @__PURE__ */ jsx7("span", { children: article.readTime })
652
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
653
+ /* @__PURE__ */ jsx8(Clock2, { className: "h-3 w-3" }),
654
+ /* @__PURE__ */ jsx8("span", { children: article.readTime })
548
655
  ] })
549
656
  ] }),
550
- /* @__PURE__ */ jsx7(
551
- Link5,
657
+ /* @__PURE__ */ jsx8(
658
+ Link6,
552
659
  {
553
660
  href: `/articles/${article.slug}`,
554
661
  className: "inline-flex items-center justify-center h-8 w-8 p-0 rounded-md hover:bg-accent transition-colors",
555
662
  "aria-label": `Read ${article.title}`,
556
- children: /* @__PURE__ */ jsx7(ArrowRight2, { className: "h-4 w-4" })
663
+ children: /* @__PURE__ */ jsx8(ArrowRight2, { className: "h-4 w-4" })
557
664
  }
558
665
  )
559
666
  ] })
@@ -561,18 +668,114 @@ function ArticleCard({ article }) {
561
668
  ] });
562
669
  }
563
670
 
671
+ // src/PaginationNav.tsx
672
+ import Link7 from "next/link";
673
+ import { ChevronLeft, ChevronRight } from "lucide-react";
674
+
675
+ // src/pagination.ts
676
+ function buildPageUrl(basePath, page) {
677
+ const base = basePath.replace(/\/$/, "");
678
+ return page > 1 ? `${base}/page/${page}` : base;
679
+ }
680
+ function buildPaginationLinks(basePath, page, totalPages) {
681
+ return {
682
+ canonicalUrl: buildPageUrl(basePath, page),
683
+ prevUrl: page > 1 ? buildPageUrl(basePath, page - 1) : null,
684
+ nextUrl: page < totalPages ? buildPageUrl(basePath, page + 1) : null
685
+ };
686
+ }
687
+
688
+ // src/PaginationNav.tsx
689
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
690
+ function PaginationNav({ basePath, page, totalPages }) {
691
+ if (totalPages <= 1) return null;
692
+ const { prevUrl, nextUrl } = buildPaginationLinks(basePath, page, totalPages);
693
+ return /* @__PURE__ */ jsxs9(
694
+ "nav",
695
+ {
696
+ className: "mt-10 flex items-center justify-center gap-4",
697
+ "aria-label": "Article listing pagination",
698
+ children: [
699
+ prevUrl && /* @__PURE__ */ jsx9("link", { rel: "prev", href: prevUrl }),
700
+ nextUrl && /* @__PURE__ */ jsx9("link", { rel: "next", href: nextUrl }),
701
+ prevUrl ? /* @__PURE__ */ jsxs9(
702
+ Link7,
703
+ {
704
+ href: prevUrl,
705
+ 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",
706
+ children: [
707
+ /* @__PURE__ */ jsx9(ChevronLeft, { className: "h-4 w-4", "aria-hidden": "true" }),
708
+ "Previous"
709
+ ]
710
+ }
711
+ ) : /* @__PURE__ */ jsxs9(
712
+ "span",
713
+ {
714
+ "aria-hidden": "true",
715
+ className: "inline-flex items-center gap-1 px-4 py-2 rounded-md font-medium text-sm text-muted-foreground opacity-50",
716
+ children: [
717
+ /* @__PURE__ */ jsx9(ChevronLeft, { className: "h-4 w-4" }),
718
+ "Previous"
719
+ ]
720
+ }
721
+ ),
722
+ /* @__PURE__ */ jsxs9("span", { className: "text-sm text-muted-foreground", children: [
723
+ "Page ",
724
+ page,
725
+ " of ",
726
+ totalPages
727
+ ] }),
728
+ nextUrl ? /* @__PURE__ */ jsxs9(
729
+ Link7,
730
+ {
731
+ href: nextUrl,
732
+ 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",
733
+ children: [
734
+ "Next",
735
+ /* @__PURE__ */ jsx9(ChevronRight, { className: "h-4 w-4", "aria-hidden": "true" })
736
+ ]
737
+ }
738
+ ) : /* @__PURE__ */ jsxs9(
739
+ "span",
740
+ {
741
+ "aria-hidden": "true",
742
+ className: "inline-flex items-center gap-1 px-4 py-2 rounded-md font-medium text-sm text-muted-foreground opacity-50",
743
+ children: [
744
+ "Next",
745
+ /* @__PURE__ */ jsx9(ChevronRight, { className: "h-4 w-4" })
746
+ ]
747
+ }
748
+ )
749
+ ]
750
+ }
751
+ );
752
+ }
753
+
564
754
  // src/LatestArticles.tsx
565
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
566
- function LatestArticles({ articles, pageSize }) {
755
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
756
+ function LatestArticles({ articles, pageSize, pagination }) {
567
757
  const [visibleCount, setVisibleCount] = useState2(pageSize);
568
758
  useEffect2(() => {
569
759
  setVisibleCount(pageSize);
570
760
  }, [articles, pageSize]);
761
+ if (pagination) {
762
+ return /* @__PURE__ */ jsxs10("div", { children: [
763
+ /* @__PURE__ */ jsx10("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-8", children: articles.map((article) => /* @__PURE__ */ jsx10(ArticleCard, { article }, article.slug)) }),
764
+ /* @__PURE__ */ jsx10(
765
+ PaginationNav,
766
+ {
767
+ basePath: pagination.basePath,
768
+ page: pagination.page,
769
+ totalPages: pagination.totalPages
770
+ }
771
+ )
772
+ ] });
773
+ }
571
774
  const visible = articles.slice(0, visibleCount);
572
775
  const hasMore = visibleCount < articles.length;
573
- return /* @__PURE__ */ jsxs8("div", { children: [
574
- /* @__PURE__ */ jsx8("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-8", children: visible.map((article) => /* @__PURE__ */ jsx8(ArticleCard, { article }, article.slug)) }),
575
- hasMore && /* @__PURE__ */ jsx8("div", { className: "mt-10 text-center", children: /* @__PURE__ */ jsx8(
776
+ return /* @__PURE__ */ jsxs10("div", { children: [
777
+ /* @__PURE__ */ jsx10("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-8", children: visible.map((article) => /* @__PURE__ */ jsx10(ArticleCard, { article }, article.slug)) }),
778
+ hasMore && /* @__PURE__ */ jsx10("div", { className: "mt-10 text-center", children: /* @__PURE__ */ jsx10(
576
779
  "button",
577
780
  {
578
781
  type: "button",
@@ -585,35 +788,36 @@ function LatestArticles({ articles, pageSize }) {
585
788
  }
586
789
 
587
790
  // src/LatestArticlesSection.tsx
588
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
791
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
589
792
  function DescriptionText({
590
793
  searchQuery,
591
794
  count
592
795
  }) {
593
796
  const pluralSuffix = count === 1 ? "" : "s";
594
797
  const text = searchQuery ? `Found ${count} article${pluralSuffix} matching your search.` : "Stay informed with our latest insights on voter engagement, campaign strategies, and civic technology.";
595
- return /* @__PURE__ */ jsx9("p", { className: "text-lg text-muted-foreground", children: text });
798
+ return /* @__PURE__ */ jsx11("p", { className: "text-lg text-muted-foreground", children: text });
596
799
  }
597
800
  function LatestArticlesSection({
598
801
  articles,
599
802
  searchQuery,
600
803
  onClearSearch,
601
- pageSize = 6
804
+ pageSize = 6,
805
+ pagination
602
806
  }) {
603
- return /* @__PURE__ */ jsx9("section", { id: "latest", className: "py-16 bg-muted/50", children: /* @__PURE__ */ jsxs9("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
604
- /* @__PURE__ */ jsxs9("div", { className: "text-center mb-12", children: [
605
- /* @__PURE__ */ jsx9("h2", { className: "text-3xl font-bold text-foreground mb-4", children: searchQuery ? "Search Results" : "Latest Articles" }),
606
- /* @__PURE__ */ jsx9(DescriptionText, { searchQuery, count: articles.length })
807
+ return /* @__PURE__ */ jsx11("section", { id: "latest", className: "py-16 bg-muted/50", children: /* @__PURE__ */ jsxs11("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
808
+ /* @__PURE__ */ jsxs11("div", { className: "text-center mb-12", children: [
809
+ /* @__PURE__ */ jsx11("h2", { className: "text-3xl font-bold text-foreground mb-4", children: searchQuery ? "Search Results" : "Latest Articles" }),
810
+ /* @__PURE__ */ jsx11(DescriptionText, { searchQuery, count: articles.length })
607
811
  ] }),
608
- articles.length === 0 && searchQuery ? /* @__PURE__ */ jsx9("div", { className: "text-center py-12", children: /* @__PURE__ */ jsxs9("div", { className: "max-w-md mx-auto", children: [
609
- /* @__PURE__ */ jsx9(Search2, { className: "h-12 w-12 text-muted-foreground mx-auto mb-4" }),
610
- /* @__PURE__ */ jsx9("h3", { className: "text-lg font-semibold text-foreground mb-2", children: "No articles found" }),
611
- /* @__PURE__ */ jsxs9("p", { className: "text-muted-foreground mb-4", children: [
812
+ articles.length === 0 && searchQuery ? /* @__PURE__ */ jsx11("div", { className: "text-center py-12", children: /* @__PURE__ */ jsxs11("div", { className: "max-w-md mx-auto", children: [
813
+ /* @__PURE__ */ jsx11(Search2, { className: "h-12 w-12 text-muted-foreground mx-auto mb-4" }),
814
+ /* @__PURE__ */ jsx11("h3", { className: "text-lg font-semibold text-foreground mb-2", children: "No articles found" }),
815
+ /* @__PURE__ */ jsxs11("p", { className: "text-muted-foreground mb-4", children: [
612
816
  `We couldn't find any articles matching "`,
613
817
  searchQuery,
614
818
  '". Try adjusting your search terms.'
615
819
  ] }),
616
- /* @__PURE__ */ jsx9(
820
+ /* @__PURE__ */ jsx11(
617
821
  "button",
618
822
  {
619
823
  type: "button",
@@ -622,7 +826,15 @@ function LatestArticlesSection({
622
826
  children: "Clear search"
623
827
  }
624
828
  )
625
- ] }) }) : /* @__PURE__ */ jsx9(LatestArticles, { articles, pageSize }, searchQuery)
829
+ ] }) }) : /* @__PURE__ */ jsx11(
830
+ LatestArticles,
831
+ {
832
+ articles,
833
+ pageSize,
834
+ pagination: searchQuery ? void 0 : pagination
835
+ },
836
+ searchQuery
837
+ )
626
838
  ] }) });
627
839
  }
628
840
 
@@ -648,12 +860,12 @@ function getBreadcrumbsConfig(config) {
648
860
 
649
861
  // src/useArticles.ts
650
862
  import { useState as useState3, useCallback, useEffect as useEffect3, useRef } from "react";
651
- function useArticles() {
863
+ function useArticles(initialArticles, initialCategories) {
652
864
  const [searchQuery, setSearchQuery] = useState3("");
653
865
  const debounceRef = useRef(null);
654
- const [articles, setArticles] = useState3([]);
655
- const [categories, setCategories] = useState3([]);
656
- const [loading, setLoading] = useState3(true);
866
+ const [articles, setArticles] = useState3(initialArticles != null ? initialArticles : []);
867
+ const [categories, setCategories] = useState3(initialCategories != null ? initialCategories : []);
868
+ const [loading, setLoading] = useState3(initialArticles === void 0);
657
869
  const [error, setError] = useState3(null);
658
870
  const fetchArticles = useCallback((query) => __async(this, null, function* () {
659
871
  try {
@@ -691,7 +903,7 @@ function useArticles() {
691
903
  }
692
904
  }), []);
693
905
  useEffect3(() => {
694
- fetchArticles("");
906
+ if (initialArticles === void 0) fetchArticles("");
695
907
  }, [fetchArticles]);
696
908
  const handleSearch = useCallback(
697
909
  (query) => {
@@ -715,7 +927,7 @@ function useArticles() {
715
927
  }
716
928
 
717
929
  // src/ArticlesPage.tsx
718
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
930
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
719
931
  function buildThemeVars(theme) {
720
932
  if (!theme) return {};
721
933
  const vars = {};
@@ -744,11 +956,12 @@ function renderSection(section, ctx, config) {
744
956
  error,
745
957
  handleSearch,
746
958
  pageSize,
747
- categoriesPageSize
959
+ categoriesPageSize,
960
+ pagination
748
961
  } = ctx;
749
962
  switch (section) {
750
963
  case "hero":
751
- return /* @__PURE__ */ jsx10(
964
+ return /* @__PURE__ */ jsx12(
752
965
  ArticlesHero,
753
966
  {
754
967
  title: (_a = config.hero) == null ? void 0 : _a.title,
@@ -757,7 +970,7 @@ function renderSection(section, ctx, config) {
757
970
  "hero"
758
971
  );
759
972
  case "search":
760
- return /* @__PURE__ */ jsx10(
973
+ return /* @__PURE__ */ jsx12(
761
974
  ArticleSearchBar,
762
975
  {
763
976
  value: searchQuery,
@@ -768,7 +981,7 @@ function renderSection(section, ctx, config) {
768
981
  "search"
769
982
  );
770
983
  case "featured":
771
- return articles.length > 0 && !searchQuery ? /* @__PURE__ */ jsx10(
984
+ return articles.length > 0 && !searchQuery ? /* @__PURE__ */ jsx12(
772
985
  FeaturedArticle,
773
986
  {
774
987
  article: articles[0],
@@ -778,16 +991,16 @@ function renderSection(section, ctx, config) {
778
991
  ) : null;
779
992
  case "latest":
780
993
  if (loading && articles.length === 0) {
781
- return /* @__PURE__ */ jsx10("section", { id: "latest", className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ jsx10("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center py-24", children: /* @__PURE__ */ jsxs10("div", { className: "text-center", children: [
782
- /* @__PURE__ */ jsx10("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4" }),
783
- /* @__PURE__ */ jsx10("p", { className: "text-muted-foreground", children: "Loading articles..." })
994
+ return /* @__PURE__ */ jsx12("section", { id: "latest", className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ jsx12("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center py-24", children: /* @__PURE__ */ jsxs12("div", { className: "text-center", children: [
995
+ /* @__PURE__ */ jsx12("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4" }),
996
+ /* @__PURE__ */ jsx12("p", { className: "text-muted-foreground", children: "Loading articles..." })
784
997
  ] }) }) }, "latest");
785
998
  }
786
999
  if (error) {
787
- return /* @__PURE__ */ jsx10("section", { id: "latest", className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ jsx10("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center", children: /* @__PURE__ */ jsxs10("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6 max-w-md text-center", children: [
788
- /* @__PURE__ */ jsx10("h3", { className: "text-lg font-semibold text-red-800 mb-2", children: "Error Loading Articles" }),
789
- /* @__PURE__ */ jsx10("p", { className: "text-red-600 mb-4", children: error }),
790
- /* @__PURE__ */ jsx10(
1000
+ return /* @__PURE__ */ jsx12("section", { id: "latest", className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ jsx12("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center", children: /* @__PURE__ */ jsxs12("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6 max-w-md text-center", children: [
1001
+ /* @__PURE__ */ jsx12("h3", { className: "text-lg font-semibold text-red-800 mb-2", children: "Error Loading Articles" }),
1002
+ /* @__PURE__ */ jsx12("p", { className: "text-red-600 mb-4", children: error }),
1003
+ /* @__PURE__ */ jsx12(
791
1004
  "button",
792
1005
  {
793
1006
  type: "button",
@@ -798,18 +1011,19 @@ function renderSection(section, ctx, config) {
798
1011
  )
799
1012
  ] }) }) }, "latest");
800
1013
  }
801
- return /* @__PURE__ */ jsx10(
1014
+ return /* @__PURE__ */ jsx12(
802
1015
  LatestArticlesSection,
803
1016
  {
804
1017
  articles: displayedArticles,
805
1018
  searchQuery,
806
1019
  onClearSearch: () => handleSearch(""),
807
- pageSize
1020
+ pageSize,
1021
+ pagination
808
1022
  },
809
1023
  "latest"
810
1024
  );
811
1025
  case "categories":
812
- return searchQuery ? null : /* @__PURE__ */ jsx10(
1026
+ return searchQuery ? null : /* @__PURE__ */ jsx12(
813
1027
  ArticleCategoryGrid,
814
1028
  {
815
1029
  categories,
@@ -822,26 +1036,48 @@ function renderSection(section, ctx, config) {
822
1036
  return null;
823
1037
  }
824
1038
  }
825
- function ArticlesPage({ config }) {
1039
+ function ArticlesPage({
1040
+ config,
1041
+ initialArticles,
1042
+ initialCategories,
1043
+ page,
1044
+ totalPages,
1045
+ totalCount
1046
+ }) {
826
1047
  var _a, _b, _c;
827
- const state = useArticles();
1048
+ const state = useArticles(initialArticles, initialCategories);
828
1049
  const layout = (_a = config.layout) != null ? _a : DEFAULT_LAYOUT;
829
1050
  const pageSize = (_b = config.pageSize) != null ? _b : DEFAULT_PAGE_SIZE;
830
1051
  const categoriesPageSize = (_c = config.categoriesPageSize) != null ? _c : DEFAULT_CATEGORIES_PAGE_SIZE;
831
1052
  const themeVars = buildThemeVars(config.theme);
832
- const hasFeatured = layout.includes("featured");
1053
+ const isPagesMode = config.listingPagination === "pages" && page !== void 0 && totalPages !== void 0;
1054
+ const currentPage = page != null ? page : 1;
1055
+ const effectiveLayout = isPagesMode && currentPage > 1 ? layout.filter((section) => section !== "featured") : layout;
1056
+ const hasFeatured = effectiveLayout.includes("featured");
833
1057
  const articlesWithoutFeatured = hasFeatured ? state.articles.slice(1) : state.articles;
834
1058
  const displayedArticles = state.searchQuery ? state.articles : articlesWithoutFeatured;
835
- const ctx = __spreadProps(__spreadValues({}, state), { displayedArticles, pageSize, categoriesPageSize });
836
- return /* @__PURE__ */ jsxs10("div", { style: themeVars, children: [
837
- layout.map((section) => renderSection(section, ctx, config)),
838
- state.articles.length > 0 && /* @__PURE__ */ jsx10(
1059
+ const pagination = isPagesMode ? { page: currentPage, totalPages, basePath: "/articles" } : void 0;
1060
+ const ctx = __spreadProps(__spreadValues({}, state), {
1061
+ displayedArticles,
1062
+ pageSize,
1063
+ categoriesPageSize,
1064
+ pagination
1065
+ });
1066
+ const articleCount = totalCount != null ? totalCount : state.articles.length;
1067
+ return /* @__PURE__ */ jsxs12("div", { style: themeVars, children: [
1068
+ effectiveLayout.map((section) => renderSection(section, ctx, config)),
1069
+ state.articles.length > 0 && /* @__PURE__ */ jsx12(
839
1070
  CollectionPageSchema,
840
1071
  {
841
1072
  title: `Articles | ${config.siteName}`,
842
- description: `Browse all ${state.articles.length} articles on ${config.siteName}.`,
1073
+ description: `Browse all ${articleCount} articles on ${config.siteName}.`,
843
1074
  url: `${config.siteUrl}/articles`,
844
- articleCount: state.articles.length
1075
+ articleCount,
1076
+ items: displayedArticles.slice(0, pageSize).map((article, index) => ({
1077
+ position: index + 1,
1078
+ url: `${config.siteUrl}/articles/${article.slug}`,
1079
+ name: article.title
1080
+ }))
845
1081
  }
846
1082
  )
847
1083
  ] });
@@ -849,10 +1085,10 @@ function ArticlesPage({ config }) {
849
1085
 
850
1086
  // src/ArticleDetailHero.tsx
851
1087
  import { Fragment as Fragment3 } from "react";
852
- import Image4 from "next/image";
853
- import Link6 from "next/link";
1088
+ import Image5 from "next/image";
1089
+ import Link8 from "next/link";
854
1090
  import { Calendar as Calendar3, Clock as Clock3, User as User2 } from "lucide-react";
855
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1091
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
856
1092
  function toSlug(cat) {
857
1093
  return cat.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
858
1094
  }
@@ -865,7 +1101,7 @@ function ArticleDetailHero({
865
1101
  }) {
866
1102
  const showConfiguredAuthors = showAuthor && authors.length > 0;
867
1103
  const showLegacyAuthor = showAuthor && authors.length === 0 && article.author.trim().length > 0;
868
- return /* @__PURE__ */ jsxs11(
1104
+ return /* @__PURE__ */ jsxs13(
869
1105
  "section",
870
1106
  {
871
1107
  style: {
@@ -877,8 +1113,8 @@ function ArticleDetailHero({
877
1113
  overflow: "hidden"
878
1114
  },
879
1115
  children: [
880
- /* @__PURE__ */ jsx11(
881
- Image4,
1116
+ /* @__PURE__ */ jsx13(
1117
+ Image5,
882
1118
  {
883
1119
  src: article.featuredImage,
884
1120
  alt: article.title,
@@ -889,7 +1125,7 @@ function ArticleDetailHero({
889
1125
  priority: true
890
1126
  }
891
1127
  ),
892
- /* @__PURE__ */ jsx11(
1128
+ /* @__PURE__ */ jsx13(
893
1129
  "div",
894
1130
  {
895
1131
  style: {
@@ -899,7 +1135,7 @@ function ArticleDetailHero({
899
1135
  }
900
1136
  }
901
1137
  ),
902
- /* @__PURE__ */ jsxs11(
1138
+ /* @__PURE__ */ jsxs13(
903
1139
  "div",
904
1140
  {
905
1141
  style: {
@@ -913,7 +1149,7 @@ function ArticleDetailHero({
913
1149
  width: "100%"
914
1150
  },
915
1151
  children: [
916
- article.categories && article.categories.length > 0 && /* @__PURE__ */ jsx11(
1152
+ article.categories && article.categories.length > 0 && /* @__PURE__ */ jsx13(
917
1153
  "div",
918
1154
  {
919
1155
  style: {
@@ -924,15 +1160,15 @@ function ArticleDetailHero({
924
1160
  marginBottom: "1rem"
925
1161
  },
926
1162
  children: article.categories.slice(0, 4).map(
927
- (cat) => categoryBasePath ? /* @__PURE__ */ jsx11(
928
- Link6,
1163
+ (cat) => categoryBasePath ? /* @__PURE__ */ jsx13(
1164
+ Link8,
929
1165
  {
930
1166
  href: `${categoryBasePath}/${toSlug(cat)}`,
931
1167
  className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full hover:bg-white/30 transition-colors",
932
1168
  children: cat
933
1169
  },
934
1170
  cat
935
- ) : /* @__PURE__ */ jsx11(
1171
+ ) : /* @__PURE__ */ jsx13(
936
1172
  "span",
937
1173
  {
938
1174
  className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full",
@@ -943,8 +1179,8 @@ function ArticleDetailHero({
943
1179
  )
944
1180
  }
945
1181
  ),
946
- /* @__PURE__ */ jsx11("h1", { className: "text-4xl md:text-5xl font-bold mb-4", children: article.title }),
947
- /* @__PURE__ */ jsxs11(
1182
+ /* @__PURE__ */ jsx13("h1", { className: "text-4xl md:text-5xl font-bold mb-4", children: article.title }),
1183
+ /* @__PURE__ */ jsxs13(
948
1184
  "div",
949
1185
  {
950
1186
  style: {
@@ -956,23 +1192,23 @@ function ArticleDetailHero({
956
1192
  color: "rgba(255,255,255,0.8)"
957
1193
  },
958
1194
  children: [
959
- showDate && article.date && /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
960
- /* @__PURE__ */ jsx11(Calendar3, { className: "h-4 w-4" }),
961
- /* @__PURE__ */ jsx11("span", { children: article.date })
1195
+ showDate && article.date && /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1196
+ /* @__PURE__ */ jsx13(Calendar3, { className: "h-4 w-4" }),
1197
+ /* @__PURE__ */ jsx13("span", { children: article.date })
962
1198
  ] }),
963
- /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
964
- /* @__PURE__ */ jsx11(Clock3, { className: "h-4 w-4" }),
965
- /* @__PURE__ */ jsx11("span", { children: article.readTime })
1199
+ /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1200
+ /* @__PURE__ */ jsx13(Clock3, { className: "h-4 w-4" }),
1201
+ /* @__PURE__ */ jsx13("span", { children: article.readTime })
966
1202
  ] }),
967
- showConfiguredAuthors && /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
968
- /* @__PURE__ */ jsx11(User2, { className: "h-4 w-4" }),
969
- /* @__PURE__ */ jsxs11("span", { children: [
1203
+ showConfiguredAuthors && /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1204
+ /* @__PURE__ */ jsx13(User2, { className: "h-4 w-4" }),
1205
+ /* @__PURE__ */ jsxs13("span", { children: [
970
1206
  "By",
971
1207
  " ",
972
- authors.map((author, index) => /* @__PURE__ */ jsxs11(Fragment3, { children: [
1208
+ authors.map((author, index) => /* @__PURE__ */ jsxs13(Fragment3, { children: [
973
1209
  index > 0 && ", ",
974
- /* @__PURE__ */ jsx11(
975
- Link6,
1210
+ /* @__PURE__ */ jsx13(
1211
+ Link8,
976
1212
  {
977
1213
  href: `/articles/authors/${author.slug}`,
978
1214
  className: "font-medium text-white underline-offset-4 hover:underline",
@@ -982,15 +1218,26 @@ function ArticleDetailHero({
982
1218
  ] }, author.slug))
983
1219
  ] })
984
1220
  ] }),
985
- showLegacyAuthor && /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
986
- /* @__PURE__ */ jsx11(User2, { className: "h-4 w-4" }),
987
- /* @__PURE__ */ jsxs11("span", { children: [
1221
+ showLegacyAuthor && /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1222
+ /* @__PURE__ */ jsx13(User2, { className: "h-4 w-4" }),
1223
+ /* @__PURE__ */ jsxs13("span", { children: [
988
1224
  "By ",
989
1225
  article.author
990
1226
  ] })
991
1227
  ] })
992
1228
  ]
993
1229
  }
1230
+ ),
1231
+ showConfiguredAuthors && authors.length === 1 && authors[0].promise && /* @__PURE__ */ jsx13(
1232
+ "p",
1233
+ {
1234
+ style: {
1235
+ marginTop: "0.75rem",
1236
+ fontSize: "0.9375rem",
1237
+ color: "rgba(255,255,255,0.85)"
1238
+ },
1239
+ children: authors[0].promise
1240
+ }
994
1241
  )
995
1242
  ]
996
1243
  }
@@ -1001,9 +1248,9 @@ function ArticleDetailHero({
1001
1248
  }
1002
1249
 
1003
1250
  // src/CategoryArticlesPage.tsx
1004
- import Image5 from "next/image";
1005
- import Link7 from "next/link";
1006
- import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1251
+ import Image6 from "next/image";
1252
+ import Link9 from "next/link";
1253
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1007
1254
  function getCategoryDescription(config, slug, categoryName) {
1008
1255
  var _a;
1009
1256
  const raw = (_a = config.categoryDescriptions) == null ? void 0 : _a[slug];
@@ -1045,7 +1292,14 @@ function buildCategoryBreadcrumbEntry(entry, context) {
1045
1292
  }
1046
1293
  return [{ name: context.categoryName }];
1047
1294
  }
1048
- function CategoryArticlesPage({ category, articles, config }) {
1295
+ function CategoryArticlesPage({
1296
+ category,
1297
+ articles,
1298
+ config,
1299
+ page,
1300
+ totalPages,
1301
+ totalCount
1302
+ }) {
1049
1303
  var _a;
1050
1304
  if (articles.length === 0) return null;
1051
1305
  const categoryName = articles[0].category;
@@ -1054,8 +1308,25 @@ function CategoryArticlesPage({ category, articles, config }) {
1054
1308
  const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1055
1309
  const breadcrumbConfig = getBreadcrumbsConfig(config);
1056
1310
  const breadcrumbItems = buildCategoryBreadcrumbItems(config, categoryName);
1057
- return /* @__PURE__ */ jsxs12("div", { children: [
1058
- breadcrumbsAreEnabled(config) && /* @__PURE__ */ jsx12(
1311
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1312
+ const articleCount = totalCount != null ? totalCount : articles.length;
1313
+ const pagination = config.listingPagination === "pages" && page !== void 0 && totalPages !== void 0 ? { page, totalPages, basePath: `/articles/category/${category}` } : void 0;
1314
+ return /* @__PURE__ */ jsxs14("div", { children: [
1315
+ /* @__PURE__ */ jsx14(
1316
+ CollectionPageSchema,
1317
+ {
1318
+ title: `${categoryName} | ${config.siteName}`,
1319
+ description: description.short,
1320
+ url: `${siteUrl}/articles/category/${category}`,
1321
+ articleCount,
1322
+ items: articles.slice(0, pageSize).map((article, index) => ({
1323
+ position: index + 1,
1324
+ url: `${siteUrl}/articles/${article.slug}`,
1325
+ name: article.title
1326
+ }))
1327
+ }
1328
+ ),
1329
+ breadcrumbsAreEnabled(config) && /* @__PURE__ */ jsx14(
1059
1330
  Breadcrumb,
1060
1331
  {
1061
1332
  items: breadcrumbItems,
@@ -1063,7 +1334,7 @@ function CategoryArticlesPage({ category, articles, config }) {
1063
1334
  showSchema: breadcrumbConfig.showSchema !== false
1064
1335
  }
1065
1336
  ),
1066
- /* @__PURE__ */ jsxs12(
1337
+ /* @__PURE__ */ jsxs14(
1067
1338
  "section",
1068
1339
  {
1069
1340
  style: {
@@ -1075,8 +1346,8 @@ function CategoryArticlesPage({ category, articles, config }) {
1075
1346
  overflow: "hidden"
1076
1347
  },
1077
1348
  children: [
1078
- /* @__PURE__ */ jsx12(
1079
- Image5,
1349
+ /* @__PURE__ */ jsx14(
1350
+ Image6,
1080
1351
  {
1081
1352
  src: heroImage,
1082
1353
  alt: categoryName,
@@ -1086,8 +1357,8 @@ function CategoryArticlesPage({ category, articles, config }) {
1086
1357
  priority: true
1087
1358
  }
1088
1359
  ),
1089
- /* @__PURE__ */ jsx12("div", { style: { position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.6)" } }),
1090
- /* @__PURE__ */ jsxs12(
1360
+ /* @__PURE__ */ jsx14("div", { style: { position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.6)" } }),
1361
+ /* @__PURE__ */ jsxs14(
1091
1362
  "div",
1092
1363
  {
1093
1364
  style: {
@@ -1098,10 +1369,10 @@ function CategoryArticlesPage({ category, articles, config }) {
1098
1369
  padding: "0 1rem"
1099
1370
  },
1100
1371
  children: [
1101
- /* @__PURE__ */ jsx12("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
1102
- /* @__PURE__ */ jsx12("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
1103
- description.long && /* @__PURE__ */ jsx12("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
1104
- /* @__PURE__ */ jsxs12("p", { className: "text-lg opacity-70 mt-2", children: [
1372
+ /* @__PURE__ */ jsx14("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
1373
+ /* @__PURE__ */ jsx14("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
1374
+ description.long && /* @__PURE__ */ jsx14("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
1375
+ /* @__PURE__ */ jsxs14("p", { className: "text-lg opacity-70 mt-2", children: [
1105
1376
  articles.length,
1106
1377
  " article",
1107
1378
  articles.length === 1 ? "" : "s"
@@ -1112,150 +1383,75 @@ function CategoryArticlesPage({ category, articles, config }) {
1112
1383
  ]
1113
1384
  }
1114
1385
  ),
1115
- /* @__PURE__ */ jsx12("section", { className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ jsxs12("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
1116
- /* @__PURE__ */ jsxs12("div", { className: "mb-8 flex items-center justify-between", children: [
1117
- /* @__PURE__ */ jsx12(Link7, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
1118
- /* @__PURE__ */ jsx12("p", { className: "text-sm text-muted-foreground", children: description.short })
1386
+ /* @__PURE__ */ jsx14("section", { className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ jsxs14("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
1387
+ /* @__PURE__ */ jsxs14("div", { className: "mb-8 flex items-center justify-between", children: [
1388
+ /* @__PURE__ */ jsx14(Link9, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
1389
+ /* @__PURE__ */ jsx14("p", { className: "text-sm text-muted-foreground", children: description.short })
1119
1390
  ] }),
1120
- /* @__PURE__ */ jsx12(LatestArticles, { articles, pageSize })
1391
+ /* @__PURE__ */ jsx14(LatestArticles, { articles, pageSize, pagination })
1121
1392
  ] }) })
1122
1393
  ] });
1123
1394
  }
1124
1395
 
1125
- // src/AuthorArticlesPage.tsx
1126
- import Link8 from "next/link";
1127
- import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1128
- function getPersonSchema(author, config, articleCount) {
1129
- var _a, _b;
1130
- return __spreadProps(__spreadValues(__spreadValues({
1131
- "@context": "https://schema.org",
1132
- "@type": "Person",
1133
- name: author.name,
1134
- description: author.bio,
1135
- url: (_a = author.url) != null ? _a : getAuthorUrl(author)
1136
- }, getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }), {
1137
- knowsAbout: config.siteName,
1138
- mainEntityOfPage: (_b = author.url) != null ? _b : `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`,
1139
- interactionStatistic: {
1140
- "@type": "InteractionCounter",
1141
- interactionType: "https://schema.org/WriteAction",
1142
- userInteractionCount: articleCount
1143
- }
1144
- });
1145
- }
1146
- function AuthorArticlesPage({ author, articles, config }) {
1396
+ // src/SeriesArticlesPage.tsx
1397
+ import Link10 from "next/link";
1398
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1399
+ function SeriesArticlesPage({ seriesSlug, articles, config }) {
1147
1400
  var _a;
1148
- const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1149
- return /* @__PURE__ */ jsxs13("div", { children: [
1150
- /* @__PURE__ */ jsx13(
1151
- "script",
1401
+ if (articles.length === 0) return null;
1402
+ const seriesName = (_a = articles[0].series) != null ? _a : seriesSlug;
1403
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1404
+ const seriesUrl = `${siteUrl}/articles/series/${seriesSlug}`;
1405
+ return /* @__PURE__ */ jsxs15("div", { children: [
1406
+ /* @__PURE__ */ jsx15(
1407
+ CollectionPageSchema,
1152
1408
  {
1153
- type: "application/ld+json",
1154
- dangerouslySetInnerHTML: {
1155
- __html: JSON.stringify(getPersonSchema(author, config, articles.length))
1156
- }
1409
+ title: `${seriesName} | ${config.siteName}`,
1410
+ description: `Follow the ${seriesName} series on ${config.siteName}.`,
1411
+ url: seriesUrl,
1412
+ articleCount: articles.length,
1413
+ items: articles.map((article, index) => ({
1414
+ position: index + 1,
1415
+ url: `${siteUrl}/articles/${article.slug}`,
1416
+ name: article.title
1417
+ }))
1157
1418
  }
1158
1419
  ),
1159
- /* @__PURE__ */ jsx13("section", { className: "bg-background py-16", children: /* @__PURE__ */ jsxs13("div", { className: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", children: [
1160
- /* @__PURE__ */ jsxs13("div", { className: "mb-8 flex items-center justify-between gap-4", children: [
1161
- /* @__PURE__ */ jsx13(Link8, { href: "/articles", className: "text-sm text-primary hover:underline", children: "All Articles" }),
1162
- /* @__PURE__ */ jsxs13("p", { className: "text-sm text-muted-foreground", children: [
1163
- "Articles by ",
1164
- author.name
1420
+ /* @__PURE__ */ jsx15("section", { className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ jsxs15("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
1421
+ /* @__PURE__ */ jsxs15("div", { className: "mb-8 flex items-center justify-between", children: [
1422
+ /* @__PURE__ */ jsx15(Link10, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
1423
+ /* @__PURE__ */ jsxs15("p", { className: "text-sm text-muted-foreground", children: [
1424
+ articles.length,
1425
+ " article",
1426
+ articles.length === 1 ? "" : "s",
1427
+ " in this series"
1165
1428
  ] })
1166
1429
  ] }),
1167
- /* @__PURE__ */ jsx13(LatestArticles, { articles, pageSize })
1430
+ /* @__PURE__ */ jsx15("h1", { className: "text-3xl font-bold text-foreground mb-8", children: seriesName }),
1431
+ /* @__PURE__ */ jsx15(
1432
+ LatestArticles,
1433
+ {
1434
+ articles,
1435
+ pageSize: Math.max(articles.length, DEFAULT_PAGE_SIZE)
1436
+ }
1437
+ )
1168
1438
  ] }) })
1169
1439
  ] });
1170
1440
  }
1171
1441
 
1172
- // src/AuthorCard.tsx
1173
- import Image6 from "next/image";
1174
- import Link9 from "next/link";
1175
- import {
1176
- Facebook,
1177
- Github,
1178
- Globe,
1179
- Instagram,
1180
- Linkedin,
1181
- Mail,
1182
- MessageCircle,
1183
- Twitter,
1184
- Youtube
1185
- } from "lucide-react";
1186
- import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1187
- function getInitials(name) {
1188
- return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
1189
- }
1190
- function AuthorCard({
1191
- author,
1192
- linkToPage = true,
1193
- showBio = false,
1194
- showSocial = false
1195
- }) {
1196
- const avatar = getAuthorAvatar(author);
1197
- const name = linkToPage ? /* @__PURE__ */ jsx14(Link9, { href: getAuthorUrl(author), className: "font-medium text-foreground hover:text-primary", children: author.name }) : /* @__PURE__ */ jsx14("span", { className: "font-medium text-foreground", children: author.name });
1198
- return /* @__PURE__ */ jsxs14("div", { className: "flex items-start gap-3 rounded-lg border border-border bg-card p-4 text-card-foreground", children: [
1199
- avatar ? /* @__PURE__ */ jsx14(
1200
- Image6,
1201
- {
1202
- src: avatar,
1203
- alt: author.name,
1204
- width: 48,
1205
- height: 48,
1206
- className: "h-12 w-12 rounded-full object-cover"
1207
- }
1208
- ) : /* @__PURE__ */ jsx14("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) }),
1209
- /* @__PURE__ */ jsxs14("div", { className: "min-w-0 flex-1", children: [
1210
- name,
1211
- showBio && author.bio && /* @__PURE__ */ jsx14("p", { className: "mt-1 line-clamp-2 text-sm text-muted-foreground", children: author.bio }),
1212
- showSocial && /* @__PURE__ */ jsx14(AuthorSocialLinks, { author })
1213
- ] })
1214
- ] });
1215
- }
1216
- var SOCIAL_ICONS = {
1217
- Website: Globe,
1218
- Facebook,
1219
- Twitter,
1220
- X: Twitter,
1221
- LinkedIn: Linkedin,
1222
- Instagram,
1223
- YouTube: Youtube,
1224
- GitHub: Github,
1225
- Newsletter: Mail
1226
- };
1227
- function getSocialIcon(label) {
1228
- var _a;
1229
- return (_a = SOCIAL_ICONS[label]) != null ? _a : MessageCircle;
1230
- }
1231
- function AuthorSocialLinks({ author }) {
1232
- const links = getAuthorSocialLinks(author);
1233
- if (links.length === 0) return null;
1234
- return /* @__PURE__ */ jsx14("div", { className: "mt-3 flex items-center gap-2", children: links.map(({ href, label }) => {
1235
- const Icon = getSocialIcon(label);
1236
- return /* @__PURE__ */ jsx14(
1237
- Link9,
1238
- {
1239
- href,
1240
- 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",
1241
- "aria-label": `${author.name} on ${label}`,
1242
- children: /* @__PURE__ */ jsx14(Icon, { className: "h-4 w-4" })
1243
- },
1244
- `${label}-${href}`
1245
- );
1246
- }) });
1247
- }
1442
+ // src/AuthorArticlesPage.tsx
1443
+ import Link11 from "next/link";
1248
1444
 
1249
1445
  // src/AuthorDetailHero.tsx
1250
1446
  import Image7 from "next/image";
1251
- import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1447
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1252
1448
  function getInitials2(name) {
1253
1449
  return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
1254
1450
  }
1255
1451
  function AuthorDetailHero({ author, articleCount }) {
1256
1452
  const avatar = getAuthorAvatar(author);
1257
- return /* @__PURE__ */ jsx15("section", { className: "bg-muted/40 py-16", children: /* @__PURE__ */ jsxs15("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: [
1258
- avatar ? /* @__PURE__ */ jsx15(
1453
+ return /* @__PURE__ */ jsx16("section", { className: "bg-muted/40 py-16", children: /* @__PURE__ */ jsxs16("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: [
1454
+ avatar ? /* @__PURE__ */ jsx16(
1259
1455
  Image7,
1260
1456
  {
1261
1457
  src: avatar,
@@ -1265,27 +1461,303 @@ function AuthorDetailHero({ author, articleCount }) {
1265
1461
  className: "h-28 w-28 rounded-full object-cover",
1266
1462
  priority: true
1267
1463
  }
1268
- ) : /* @__PURE__ */ jsx15("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) }),
1269
- /* @__PURE__ */ jsxs15("div", { children: [
1270
- /* @__PURE__ */ jsx15("p", { className: "mb-3 text-sm font-semibold uppercase tracking-widest text-muted-foreground", children: "Author" }),
1271
- /* @__PURE__ */ jsx15("h1", { className: "text-4xl font-bold text-foreground md:text-5xl", children: author.name }),
1272
- /* @__PURE__ */ jsx15("p", { className: "mx-auto mt-4 max-w-2xl text-lg text-muted-foreground", children: author.bio }),
1273
- typeof articleCount === "number" && /* @__PURE__ */ jsxs15("p", { className: "mt-3 text-sm text-muted-foreground", children: [
1464
+ ) : /* @__PURE__ */ jsx16("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) }),
1465
+ /* @__PURE__ */ jsxs16("div", { children: [
1466
+ /* @__PURE__ */ jsx16("p", { className: "mb-3 text-sm font-semibold uppercase tracking-widest text-muted-foreground", children: "Author" }),
1467
+ /* @__PURE__ */ jsx16("h1", { className: "text-4xl font-bold text-foreground md:text-5xl", children: author.name }),
1468
+ /* @__PURE__ */ jsx16("p", { className: "mx-auto mt-4 max-w-2xl text-lg text-muted-foreground", children: author.bio }),
1469
+ typeof articleCount === "number" && /* @__PURE__ */ jsxs16("p", { className: "mt-3 text-sm text-muted-foreground", children: [
1274
1470
  articleCount,
1275
1471
  " article",
1276
1472
  articleCount === 1 ? "" : "s"
1277
1473
  ] })
1278
1474
  ] }),
1279
- /* @__PURE__ */ jsx15(AuthorSocialLinks, { author })
1475
+ /* @__PURE__ */ jsx16(AuthorSocialLinks, { author })
1280
1476
  ] }) });
1281
1477
  }
1282
1478
 
1479
+ // src/eventTracking.tsx
1480
+ import { useEffect as useEffect4, useRef as useRef2 } from "react";
1481
+ import { jsx as jsx17 } from "react/jsx-runtime";
1482
+ var AVERAGE_WORDS_PER_MINUTE = 200;
1483
+ var DEFAULT_MEANINGFUL_READ_MS = 15e3;
1484
+ function ArticleViewTracker({ article, config }) {
1485
+ const firedMeaningfulRead = useRef2(false);
1486
+ useEffect4(() => {
1487
+ emitArticleEvent(config == null ? void 0 : config.onEvent, {
1488
+ name: "article_viewed",
1489
+ articleSlug: article.slug,
1490
+ category: article.category,
1491
+ seriesSlug: article.seriesSlug
1492
+ });
1493
+ const estimatedMs = article.wordCount ? article.wordCount / AVERAGE_WORDS_PER_MINUTE * 6e4 * 0.5 : DEFAULT_MEANINGFUL_READ_MS;
1494
+ const timer = setTimeout(() => {
1495
+ if (firedMeaningfulRead.current) return;
1496
+ firedMeaningfulRead.current = true;
1497
+ emitArticleEvent(config == null ? void 0 : config.onEvent, { name: "meaningful_read", articleSlug: article.slug });
1498
+ }, estimatedMs);
1499
+ return () => clearTimeout(timer);
1500
+ }, [article.slug]);
1501
+ return null;
1502
+ }
1503
+ function CtaViewTracker({ ctaId, articleSlug, config, children }) {
1504
+ const ref = useRef2(null);
1505
+ const fired = useRef2(false);
1506
+ useEffect4(() => {
1507
+ const node = ref.current;
1508
+ if (!node) return;
1509
+ function fire() {
1510
+ if (fired.current) return;
1511
+ fired.current = true;
1512
+ emitArticleEvent(config == null ? void 0 : config.onEvent, { name: "cta_viewed", ctaId, articleSlug });
1513
+ }
1514
+ if (typeof IntersectionObserver === "undefined") {
1515
+ fire();
1516
+ return;
1517
+ }
1518
+ const observer = new IntersectionObserver(
1519
+ (entries) => {
1520
+ if (entries.some((entry) => entry.isIntersecting)) fire();
1521
+ },
1522
+ { threshold: 0.5 }
1523
+ );
1524
+ observer.observe(node);
1525
+ return () => observer.disconnect();
1526
+ }, [ctaId, articleSlug]);
1527
+ return /* @__PURE__ */ jsx17("div", { ref, children });
1528
+ }
1529
+
1530
+ // src/AuthorArticlesPage.tsx
1531
+ import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
1532
+ function getPersonSchema(author, config, articleCount) {
1533
+ var _a, _b;
1534
+ return __spreadProps(__spreadValues(__spreadValues({
1535
+ "@context": "https://schema.org",
1536
+ "@type": "Person",
1537
+ name: author.name,
1538
+ description: author.bio,
1539
+ url: (_a = author.url) != null ? _a : getAuthorUrl(author)
1540
+ }, getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }), {
1541
+ knowsAbout: config.siteName,
1542
+ mainEntityOfPage: (_b = author.url) != null ? _b : `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`,
1543
+ interactionStatistic: {
1544
+ "@type": "InteractionCounter",
1545
+ interactionType: "https://schema.org/WriteAction",
1546
+ userInteractionCount: articleCount
1547
+ }
1548
+ });
1549
+ }
1550
+ function renderHeroSection(author, articleCount) {
1551
+ return /* @__PURE__ */ jsx18(AuthorDetailHero, { author, articleCount }, "hero");
1552
+ }
1553
+ function renderPromiseSection(author) {
1554
+ return author.promise ? /* @__PURE__ */ jsx18("section", { "aria-label": "Author promise", className: "bg-background py-12", children: /* @__PURE__ */ jsx18("div", { className: "mx-auto max-w-3xl px-4 text-center sm:px-6 lg:px-8", children: /* @__PURE__ */ jsx18("p", { className: "text-xl font-medium text-foreground md:text-2xl", children: author.promise }) }) }, "promise") : null;
1555
+ }
1556
+ function renderServesWhoSection(author) {
1557
+ return author.servesWho && author.servesWho.length > 0 ? /* @__PURE__ */ jsx18("section", { "aria-label": "Who this author serves", className: "bg-muted/40 py-12", children: /* @__PURE__ */ jsxs17("div", { className: "mx-auto max-w-3xl px-4 sm:px-6 lg:px-8", children: [
1558
+ /* @__PURE__ */ jsx18("h2", { className: "mb-4 text-center text-2xl font-bold text-foreground", children: "Who I help" }),
1559
+ /* @__PURE__ */ jsx18("ul", { className: "grid gap-3 sm:grid-cols-2", children: author.servesWho.map((who) => /* @__PURE__ */ jsx18(
1560
+ "li",
1561
+ {
1562
+ className: "rounded-lg border border-border bg-card px-4 py-3 text-sm text-card-foreground",
1563
+ children: who
1564
+ },
1565
+ who
1566
+ )) })
1567
+ ] }) }, "servesWho") : null;
1568
+ }
1569
+ function renderOriginStorySection(author) {
1570
+ return author.originStory && author.originStory.length > 0 ? /* @__PURE__ */ jsx18("section", { "aria-label": "Author origin story", className: "bg-background py-12", children: /* @__PURE__ */ jsxs17("div", { className: "mx-auto max-w-3xl px-4 sm:px-6 lg:px-8", children: [
1571
+ /* @__PURE__ */ jsx18("h2", { className: "mb-6 text-2xl font-bold text-foreground", children: "My story" }),
1572
+ /* @__PURE__ */ jsx18("div", { className: "space-y-6", children: author.originStory.map((block, blockIndex) => {
1573
+ var _a;
1574
+ return /* @__PURE__ */ jsxs17("div", { children: [
1575
+ block.heading && /* @__PURE__ */ jsx18("h3", { className: "mb-2 text-lg font-semibold text-foreground", children: block.heading }),
1576
+ block.paragraphs.map((paragraph, paragraphIndex) => {
1577
+ var _a2;
1578
+ return /* @__PURE__ */ jsx18(
1579
+ "p",
1580
+ {
1581
+ className: "mb-3 text-muted-foreground",
1582
+ children: paragraph
1583
+ },
1584
+ `${(_a2 = block.heading) != null ? _a2 : blockIndex}-${paragraph.slice(0, 40)}-${paragraphIndex}`
1585
+ );
1586
+ })
1587
+ ] }, (_a = block.heading) != null ? _a : blockIndex);
1588
+ }) })
1589
+ ] }) }, "originStory") : null;
1590
+ }
1591
+ function renderPrinciplesSection(author) {
1592
+ return author.principles && author.principles.length > 0 ? /* @__PURE__ */ jsx18("section", { "aria-label": "Author principles", className: "bg-muted/40 py-12", children: /* @__PURE__ */ jsxs17("div", { className: "mx-auto max-w-3xl px-4 sm:px-6 lg:px-8", children: [
1593
+ /* @__PURE__ */ jsx18("h2", { className: "mb-4 text-center text-2xl font-bold text-foreground", children: "What I believe" }),
1594
+ /* @__PURE__ */ jsx18("ul", { className: "space-y-3", children: author.principles.map((principle) => /* @__PURE__ */ jsx18(
1595
+ "li",
1596
+ {
1597
+ className: "rounded-lg border border-border bg-card px-4 py-3 text-sm text-card-foreground",
1598
+ children: principle
1599
+ },
1600
+ principle
1601
+ )) })
1602
+ ] }) }, "principles") : null;
1603
+ }
1604
+ function renderProofSection(author) {
1605
+ return author.proof && author.proof.length > 0 ? /* @__PURE__ */ jsx18("section", { "aria-label": "Author proof", className: "bg-background py-12", children: /* @__PURE__ */ jsxs17("div", { className: "mx-auto max-w-4xl px-4 sm:px-6 lg:px-8", children: [
1606
+ /* @__PURE__ */ jsx18("h2", { className: "mb-6 text-center text-2xl font-bold text-foreground", children: "Proof" }),
1607
+ /* @__PURE__ */ jsx18("div", { className: "grid gap-4 sm:grid-cols-2", children: author.proof.map((item) => {
1608
+ var _a, _b, _c;
1609
+ return /* @__PURE__ */ jsxs17(
1610
+ "div",
1611
+ {
1612
+ className: "rounded-lg border border-border bg-card p-4 text-card-foreground",
1613
+ children: [
1614
+ /* @__PURE__ */ jsx18("p", { className: "font-medium", children: item.claim }),
1615
+ (item.source || item.url) && /* @__PURE__ */ jsx18("p", { className: "mt-1 text-sm text-muted-foreground", children: item.url ? /* @__PURE__ */ jsx18(Link11, { href: item.url, className: "hover:text-primary hover:underline", children: (_c = item.source) != null ? _c : item.url }) : item.source })
1616
+ ]
1617
+ },
1618
+ `${item.claim}-${(_b = (_a = item.url) != null ? _a : item.source) != null ? _b : ""}`
1619
+ );
1620
+ }) })
1621
+ ] }) }, "proof") : null;
1622
+ }
1623
+ function renderCtaSection(author, config) {
1624
+ return author.primaryCta ? /* @__PURE__ */ jsx18(CtaViewTracker, { ctaId: `author-cta:${author.slug}`, config, children: /* @__PURE__ */ jsx18("section", { "aria-label": "Author call to action", className: "bg-muted/40 py-12", children: /* @__PURE__ */ jsx18("div", { className: "mx-auto max-w-2xl px-4 text-center sm:px-6 lg:px-8", children: /* @__PURE__ */ jsx18(
1625
+ Link11,
1626
+ {
1627
+ href: author.primaryCta.href,
1628
+ onClick: () => emitArticleEvent(config == null ? void 0 : config.onEvent, {
1629
+ name: "cta_clicked",
1630
+ ctaId: `author-cta:${author.slug}`
1631
+ }),
1632
+ 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",
1633
+ children: author.primaryCta.label
1634
+ }
1635
+ ) }) }) }, "cta") : null;
1636
+ }
1637
+ function renderArticlesSection(author, articles, pageSize, pagination) {
1638
+ return /* @__PURE__ */ jsx18("section", { "aria-label": "Articles by this author", className: "bg-background py-16", children: /* @__PURE__ */ jsxs17("div", { className: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", children: [
1639
+ /* @__PURE__ */ jsxs17("div", { className: "mb-8 flex items-center justify-between gap-4", children: [
1640
+ /* @__PURE__ */ jsx18(Link11, { href: "/articles", className: "text-sm text-primary hover:underline", children: "All Articles" }),
1641
+ /* @__PURE__ */ jsxs17("p", { className: "text-sm text-muted-foreground", children: [
1642
+ "Articles by ",
1643
+ author.name
1644
+ ] })
1645
+ ] }),
1646
+ /* @__PURE__ */ jsx18(LatestArticles, { articles, pageSize, pagination })
1647
+ ] }) }, "articles");
1648
+ }
1649
+ function renderAuthorSection(section, ctx) {
1650
+ const { author, articleCount, articles, pageSize, pagination, customSection, config } = ctx;
1651
+ switch (section) {
1652
+ case "hero":
1653
+ return renderHeroSection(author, articleCount);
1654
+ case "promise":
1655
+ return renderPromiseSection(author);
1656
+ case "servesWho":
1657
+ return renderServesWhoSection(author);
1658
+ case "originStory":
1659
+ return renderOriginStorySection(author);
1660
+ case "principles":
1661
+ return renderPrinciplesSection(author);
1662
+ case "proof":
1663
+ return renderProofSection(author);
1664
+ case "cta":
1665
+ return renderCtaSection(author, config);
1666
+ case "articles":
1667
+ return renderArticlesSection(author, articles, pageSize, pagination);
1668
+ case "custom":
1669
+ return customSection != null ? customSection : null;
1670
+ default:
1671
+ return null;
1672
+ }
1673
+ }
1674
+ function AuthorArticlesPage({
1675
+ author,
1676
+ articles,
1677
+ config,
1678
+ page,
1679
+ totalPages,
1680
+ totalCount,
1681
+ sections,
1682
+ customSection
1683
+ }) {
1684
+ var _a, _b;
1685
+ const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1686
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1687
+ const articleCount = totalCount != null ? totalCount : articles.length;
1688
+ const pagination = config.listingPagination === "pages" && page !== void 0 && totalPages !== void 0 ? { page, totalPages, basePath: `/articles/authors/${author.slug}` } : void 0;
1689
+ const schemas = /* @__PURE__ */ jsxs17(Fragment4, { children: [
1690
+ /* @__PURE__ */ jsx18(
1691
+ "script",
1692
+ {
1693
+ type: "application/ld+json",
1694
+ dangerouslySetInnerHTML: {
1695
+ __html: JSON.stringify(getPersonSchema(author, config, articleCount))
1696
+ }
1697
+ }
1698
+ ),
1699
+ /* @__PURE__ */ jsx18(
1700
+ CollectionPageSchema,
1701
+ {
1702
+ title: `${author.name} | ${config.siteName}`,
1703
+ description: `Articles by ${author.name} on ${config.siteName}.`,
1704
+ url: (_b = author.url) != null ? _b : `${siteUrl}/articles/authors/${author.slug}`,
1705
+ articleCount,
1706
+ items: articles.slice(0, pageSize).map((article, index) => ({
1707
+ position: index + 1,
1708
+ url: `${siteUrl}/articles/${article.slug}`,
1709
+ name: article.title
1710
+ }))
1711
+ }
1712
+ )
1713
+ ] });
1714
+ if (!sections) {
1715
+ return /* @__PURE__ */ jsxs17("div", { children: [
1716
+ schemas,
1717
+ /* @__PURE__ */ jsx18("section", { className: "bg-background py-16", children: /* @__PURE__ */ jsxs17("div", { className: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", children: [
1718
+ /* @__PURE__ */ jsxs17("div", { className: "mb-8 flex items-center justify-between gap-4", children: [
1719
+ /* @__PURE__ */ jsx18(Link11, { href: "/articles", className: "text-sm text-primary hover:underline", children: "All Articles" }),
1720
+ /* @__PURE__ */ jsxs17("p", { className: "text-sm text-muted-foreground", children: [
1721
+ "Articles by ",
1722
+ author.name
1723
+ ] })
1724
+ ] }),
1725
+ /* @__PURE__ */ jsx18(LatestArticles, { articles, pageSize, pagination })
1726
+ ] }) })
1727
+ ] });
1728
+ }
1729
+ const sectionCtx = {
1730
+ author,
1731
+ articleCount,
1732
+ articles,
1733
+ pageSize,
1734
+ pagination,
1735
+ customSection,
1736
+ config
1737
+ };
1738
+ return /* @__PURE__ */ jsxs17("div", { children: [
1739
+ schemas,
1740
+ sections.map((section) => renderAuthorSection(section, sectionCtx))
1741
+ ] });
1742
+ }
1743
+
1283
1744
  // src/ArticleSocialShare.tsx
1284
1745
  import { useState as useState4 } from "react";
1285
1746
  import { Share2, Copy, Check, Mail as Mail2, MessageCircle as MessageCircle2, Users, FileText } from "lucide-react";
1286
- import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1287
- function ArticleSocialShare({ title, url, excerpt, shareMessage }) {
1747
+ import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
1748
+ function ArticleSocialShare({
1749
+ title,
1750
+ url,
1751
+ excerpt,
1752
+ shareMessage,
1753
+ config,
1754
+ articleSlug
1755
+ }) {
1288
1756
  const [copied, setCopied] = useState4(false);
1757
+ function trackShare(channel) {
1758
+ if (!articleSlug) return;
1759
+ emitArticleEvent(config == null ? void 0 : config.onEvent, { name: "shared", articleSlug, channel });
1760
+ }
1289
1761
  const encodedTitle = encodeURIComponent(title);
1290
1762
  const encodedUrl = encodeURIComponent(url);
1291
1763
  const encodedExcerpt = encodeURIComponent(excerpt || "");
@@ -1302,102 +1774,203 @@ function ArticleSocialShare({ title, url, excerpt, shareMessage }) {
1302
1774
  try {
1303
1775
  yield navigator.clipboard.writeText(url);
1304
1776
  setCopied(true);
1777
+ trackShare("copy-link");
1305
1778
  setTimeout(() => setCopied(false), 2e3);
1306
1779
  } catch (e) {
1307
1780
  }
1308
1781
  });
1309
- const openShareWindow = (shareUrl) => {
1782
+ const openShareWindow = (shareUrl, channel) => {
1783
+ trackShare(channel);
1310
1784
  globalThis.open(shareUrl, "_blank", "width=600,height=400,scrollbars=yes,resizable=yes");
1311
1785
  };
1312
1786
  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";
1313
- return /* @__PURE__ */ jsxs16("div", { className: "border-t border-border pt-8 mt-8", children: [
1314
- /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 mb-4", children: [
1315
- /* @__PURE__ */ jsx16(Share2, { className: "h-5 w-5 text-muted-foreground" }),
1316
- /* @__PURE__ */ jsx16("h3", { className: "text-lg font-semibold text-foreground", children: "Share this article" })
1787
+ return /* @__PURE__ */ jsxs18("div", { className: "border-t border-border pt-8 mt-8", children: [
1788
+ /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2 mb-4", children: [
1789
+ /* @__PURE__ */ jsx19(Share2, { className: "h-5 w-5 text-muted-foreground" }),
1790
+ /* @__PURE__ */ jsx19("h3", { className: "text-lg font-semibold text-foreground", children: "Share this article" })
1317
1791
  ] }),
1318
- /* @__PURE__ */ jsxs16("div", { className: "flex flex-wrap gap-2", children: [
1319
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.linkedin), children: [
1320
- /* @__PURE__ */ jsx16(Users, { className: "h-4 w-4" }),
1321
- "LinkedIn"
1322
- ] }),
1323
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.facebook), children: [
1324
- /* @__PURE__ */ jsx16("span", { className: "text-xs font-bold", children: "f" }),
1325
- " Facebook"
1326
- ] }),
1327
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.twitter), children: [
1328
- /* @__PURE__ */ jsx16(MessageCircle2, { className: "h-4 w-4" }),
1329
- "Twitter"
1330
- ] }),
1331
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.reddit), children: [
1332
- /* @__PURE__ */ jsx16(FileText, { className: "h-4 w-4" }),
1333
- "Reddit"
1334
- ] }),
1335
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.whatsapp), children: [
1336
- /* @__PURE__ */ jsx16(MessageCircle2, { className: "h-4 w-4" }),
1337
- "WhatsApp"
1338
- ] }),
1339
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.telegram), children: [
1340
- /* @__PURE__ */ jsx16(MessageCircle2, { className: "h-4 w-4" }),
1341
- "Telegram"
1342
- ] }),
1343
- /* @__PURE__ */ jsxs16("a", { className: btnClass, href: shareLinks.email, children: [
1344
- /* @__PURE__ */ jsx16(Mail2, { className: "h-4 w-4" }),
1792
+ /* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap gap-2", children: [
1793
+ /* @__PURE__ */ jsxs18(
1794
+ "button",
1795
+ {
1796
+ type: "button",
1797
+ className: btnClass,
1798
+ onClick: () => openShareWindow(shareLinks.linkedin, "linkedin"),
1799
+ children: [
1800
+ /* @__PURE__ */ jsx19(Users, { className: "h-4 w-4" }),
1801
+ "LinkedIn"
1802
+ ]
1803
+ }
1804
+ ),
1805
+ /* @__PURE__ */ jsxs18(
1806
+ "button",
1807
+ {
1808
+ type: "button",
1809
+ className: btnClass,
1810
+ onClick: () => openShareWindow(shareLinks.facebook, "facebook"),
1811
+ children: [
1812
+ /* @__PURE__ */ jsx19("span", { className: "text-xs font-bold", children: "f" }),
1813
+ " Facebook"
1814
+ ]
1815
+ }
1816
+ ),
1817
+ /* @__PURE__ */ jsxs18(
1818
+ "button",
1819
+ {
1820
+ type: "button",
1821
+ className: btnClass,
1822
+ onClick: () => openShareWindow(shareLinks.twitter, "twitter"),
1823
+ children: [
1824
+ /* @__PURE__ */ jsx19(MessageCircle2, { className: "h-4 w-4" }),
1825
+ "Twitter"
1826
+ ]
1827
+ }
1828
+ ),
1829
+ /* @__PURE__ */ jsxs18(
1830
+ "button",
1831
+ {
1832
+ type: "button",
1833
+ className: btnClass,
1834
+ onClick: () => openShareWindow(shareLinks.reddit, "reddit"),
1835
+ children: [
1836
+ /* @__PURE__ */ jsx19(FileText, { className: "h-4 w-4" }),
1837
+ "Reddit"
1838
+ ]
1839
+ }
1840
+ ),
1841
+ /* @__PURE__ */ jsxs18(
1842
+ "button",
1843
+ {
1844
+ type: "button",
1845
+ className: btnClass,
1846
+ onClick: () => openShareWindow(shareLinks.whatsapp, "whatsapp"),
1847
+ children: [
1848
+ /* @__PURE__ */ jsx19(MessageCircle2, { className: "h-4 w-4" }),
1849
+ "WhatsApp"
1850
+ ]
1851
+ }
1852
+ ),
1853
+ /* @__PURE__ */ jsxs18(
1854
+ "button",
1855
+ {
1856
+ type: "button",
1857
+ className: btnClass,
1858
+ onClick: () => openShareWindow(shareLinks.telegram, "telegram"),
1859
+ children: [
1860
+ /* @__PURE__ */ jsx19(MessageCircle2, { className: "h-4 w-4" }),
1861
+ "Telegram"
1862
+ ]
1863
+ }
1864
+ ),
1865
+ /* @__PURE__ */ jsxs18("a", { className: btnClass, href: shareLinks.email, onClick: () => trackShare("email"), children: [
1866
+ /* @__PURE__ */ jsx19(Mail2, { className: "h-4 w-4" }),
1345
1867
  "Email"
1346
1868
  ] }),
1347
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: copyToClipboard, children: [
1348
- copied ? /* @__PURE__ */ jsx16(Check, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx16(Copy, { className: "h-4 w-4" }),
1869
+ /* @__PURE__ */ jsxs18("button", { type: "button", className: btnClass, onClick: copyToClipboard, children: [
1870
+ copied ? /* @__PURE__ */ jsx19(Check, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx19(Copy, { className: "h-4 w-4" }),
1349
1871
  copied ? "Copied!" : "Copy Link"
1350
1872
  ] })
1351
1873
  ] }),
1352
- shareMessage && /* @__PURE__ */ jsx16("p", { className: "text-sm text-muted-foreground mt-3", children: shareMessage })
1874
+ shareMessage && /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground mt-3", children: shareMessage })
1353
1875
  ] });
1354
1876
  }
1355
1877
 
1356
1878
  // src/ArticleNavigation.tsx
1357
- import Link10 from "next/link";
1358
- import { ChevronLeft, ChevronRight } from "lucide-react";
1359
- import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
1879
+ import Link12 from "next/link";
1880
+ import { ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight2 } from "lucide-react";
1881
+ import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
1360
1882
  function truncateTitle(title, maxLength = 60) {
1361
1883
  return title.length > maxLength ? `${title.substring(0, maxLength)}...` : title;
1362
1884
  }
1363
- function ArticleNavigation({ previous, next, basePath }) {
1885
+ function ArticleNavigation({
1886
+ previous,
1887
+ next,
1888
+ basePath,
1889
+ fromSlug,
1890
+ pathKey,
1891
+ config
1892
+ }) {
1364
1893
  if (!previous && !next) return null;
1365
- return /* @__PURE__ */ jsx17("nav", { className: "mt-12 pt-8 border-t border-border", "aria-label": "Article navigation", children: /* @__PURE__ */ jsxs17("div", { className: "flex justify-between gap-4", children: [
1366
- /* @__PURE__ */ jsx17("div", { className: "flex-1 min-w-0", children: previous && /* @__PURE__ */ jsxs17(
1367
- Link10,
1894
+ function emitStep(toSlug2, direction) {
1895
+ if (!pathKey || !fromSlug) return;
1896
+ emitArticleEvent(config == null ? void 0 : config.onEvent, {
1897
+ name: "path_step_advanced",
1898
+ pathKey,
1899
+ fromSlug,
1900
+ toSlug: toSlug2,
1901
+ direction
1902
+ });
1903
+ }
1904
+ return /* @__PURE__ */ jsx20("nav", { className: "mt-12 pt-8 border-t border-border", "aria-label": "Article navigation", children: /* @__PURE__ */ jsxs19("div", { className: "flex justify-between gap-4", children: [
1905
+ /* @__PURE__ */ jsx20("div", { className: "flex-1 min-w-0", children: previous && /* @__PURE__ */ jsxs19(
1906
+ Link12,
1368
1907
  {
1369
1908
  href: `${basePath}/${previous.slug}`,
1909
+ onClick: () => emitStep(previous.slug, "previous"),
1370
1910
  className: "group flex items-center gap-3 p-4 rounded-lg border border-border hover:border-primary/20 hover:bg-muted/50 transition-colors",
1371
1911
  children: [
1372
- /* @__PURE__ */ jsx17(ChevronLeft, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" }),
1373
- /* @__PURE__ */ jsxs17("div", { className: "min-w-0 flex-1", children: [
1374
- /* @__PURE__ */ jsx17("div", { className: "text-sm text-muted-foreground mb-1", children: "Previous Article" }),
1375
- /* @__PURE__ */ jsx17("div", { className: "font-medium text-foreground truncate", title: previous.title, children: truncateTitle(previous.title) })
1912
+ /* @__PURE__ */ jsx20(ChevronLeft2, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" }),
1913
+ /* @__PURE__ */ jsxs19("div", { className: "min-w-0 flex-1", children: [
1914
+ /* @__PURE__ */ jsx20("div", { className: "text-sm text-muted-foreground mb-1", children: "Previous Article" }),
1915
+ /* @__PURE__ */ jsx20("div", { className: "font-medium text-foreground truncate", title: previous.title, children: truncateTitle(previous.title) })
1376
1916
  ] })
1377
1917
  ]
1378
1918
  }
1379
1919
  ) }),
1380
- /* @__PURE__ */ jsx17("div", { className: "flex-1 min-w-0", children: next && /* @__PURE__ */ jsxs17(
1381
- Link10,
1920
+ /* @__PURE__ */ jsx20("div", { className: "flex-1 min-w-0", children: next && /* @__PURE__ */ jsxs19(
1921
+ Link12,
1382
1922
  {
1383
1923
  href: `${basePath}/${next.slug}`,
1924
+ onClick: () => emitStep(next.slug, "next"),
1384
1925
  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",
1385
1926
  children: [
1386
- /* @__PURE__ */ jsxs17("div", { className: "min-w-0 flex-1 text-right", children: [
1387
- /* @__PURE__ */ jsx17("div", { className: "text-sm text-muted-foreground mb-1", children: "Next Article" }),
1388
- /* @__PURE__ */ jsx17("div", { className: "font-medium text-foreground truncate", title: next.title, children: truncateTitle(next.title) })
1927
+ /* @__PURE__ */ jsxs19("div", { className: "min-w-0 flex-1 text-right", children: [
1928
+ /* @__PURE__ */ jsx20("div", { className: "text-sm text-muted-foreground mb-1", children: "Next Article" }),
1929
+ /* @__PURE__ */ jsx20("div", { className: "font-medium text-foreground truncate", title: next.title, children: truncateTitle(next.title) })
1389
1930
  ] }),
1390
- /* @__PURE__ */ jsx17(ChevronRight, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" })
1931
+ /* @__PURE__ */ jsx20(ChevronRight2, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" })
1391
1932
  ]
1392
1933
  }
1393
1934
  ) })
1394
1935
  ] }) });
1395
1936
  }
1396
1937
 
1938
+ // src/RelatedArticlesSection.tsx
1939
+ import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
1940
+ function RelatedArticlesSection({
1941
+ articles,
1942
+ category,
1943
+ heading,
1944
+ config,
1945
+ fromSlug,
1946
+ source = "category"
1947
+ }) {
1948
+ if (articles.length === 0) return null;
1949
+ return /* @__PURE__ */ jsxs20("section", { className: "mt-12 pt-8 border-t border-border", "aria-label": "Related articles", children: [
1950
+ /* @__PURE__ */ jsx21("h2", { className: "text-xl font-semibold text-foreground mb-6", children: heading != null ? heading : `More in ${category}` }),
1951
+ /* @__PURE__ */ jsx21("div", { className: "grid gap-6 sm:grid-cols-2 lg:grid-cols-3", children: articles.map((article) => /* @__PURE__ */ jsx21(
1952
+ "div",
1953
+ {
1954
+ onClickCapture: () => {
1955
+ if (!fromSlug) return;
1956
+ emitArticleEvent(config == null ? void 0 : config.onEvent, {
1957
+ name: "related_article_clicked",
1958
+ fromSlug,
1959
+ toSlug: article.slug,
1960
+ source
1961
+ });
1962
+ },
1963
+ children: /* @__PURE__ */ jsx21(ArticleCard, { article, config })
1964
+ },
1965
+ article.slug
1966
+ )) })
1967
+ ] });
1968
+ }
1969
+
1397
1970
  // src/ArticleBackLink.tsx
1398
- import Link11 from "next/link";
1971
+ import Link13 from "next/link";
1399
1972
  import { ArrowLeft } from "lucide-react";
1400
- import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
1973
+ import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
1401
1974
  function ArticleBackLink({
1402
1975
  config,
1403
1976
  href = "/articles",
@@ -1405,13 +1978,13 @@ function ArticleBackLink({
1405
1978
  className
1406
1979
  }) {
1407
1980
  if (config.showBackToArticles === false) return null;
1408
- return /* @__PURE__ */ jsxs18(
1409
- Link11,
1981
+ return /* @__PURE__ */ jsxs21(
1982
+ Link13,
1410
1983
  {
1411
1984
  href,
1412
1985
  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",
1413
1986
  children: [
1414
- /* @__PURE__ */ jsx18(ArrowLeft, { className: "h-4 w-4" }),
1987
+ /* @__PURE__ */ jsx22(ArrowLeft, { className: "h-4 w-4" }),
1415
1988
  label
1416
1989
  ]
1417
1990
  }
@@ -1419,17 +1992,17 @@ function ArticleBackLink({
1419
1992
  }
1420
1993
 
1421
1994
  // src/ArticleTOC.tsx
1422
- import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
1995
+ import { jsx as jsx23, jsxs as jsxs22 } from "react/jsx-runtime";
1423
1996
  function ArticleTOC({ toc, className }) {
1424
1997
  if (!toc.length) return null;
1425
- return /* @__PURE__ */ jsxs19(
1998
+ return /* @__PURE__ */ jsxs22(
1426
1999
  "nav",
1427
2000
  {
1428
2001
  "aria-label": "Table of contents",
1429
2002
  className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
1430
2003
  children: [
1431
- /* @__PURE__ */ jsx19("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
1432
- /* @__PURE__ */ jsx19("ul", { className: "space-y-1 text-sm", children: toc.map((item) => /* @__PURE__ */ jsx19("li", { style: { paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }, children: /* @__PURE__ */ jsx19(
2004
+ /* @__PURE__ */ jsx23("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
2005
+ /* @__PURE__ */ jsx23("ul", { className: "space-y-1 text-sm", children: toc.map((item) => /* @__PURE__ */ jsx23("li", { style: { paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }, children: /* @__PURE__ */ jsx23(
1433
2006
  "a",
1434
2007
  {
1435
2008
  href: `#${item.id}`,
@@ -1443,35 +2016,35 @@ function ArticleTOC({ toc, className }) {
1443
2016
  }
1444
2017
 
1445
2018
  // src/ScrollToTop.tsx
1446
- import { useEffect as useEffect4, useState as useState5 } from "react";
2019
+ import { useEffect as useEffect5, useState as useState5 } from "react";
1447
2020
  import { ArrowUp } from "lucide-react";
1448
- import { jsx as jsx20 } from "react/jsx-runtime";
2021
+ import { jsx as jsx24 } from "react/jsx-runtime";
1449
2022
  function ScrollToTop() {
1450
2023
  const [visible, setVisible] = useState5(false);
1451
- useEffect4(() => {
2024
+ useEffect5(() => {
1452
2025
  const onScroll = () => setVisible(globalThis.scrollY > 300);
1453
2026
  globalThis.addEventListener("scroll", onScroll, { passive: true });
1454
2027
  return () => globalThis.removeEventListener("scroll", onScroll);
1455
2028
  }, []);
1456
2029
  if (!visible) return null;
1457
- return /* @__PURE__ */ jsx20(
2030
+ return /* @__PURE__ */ jsx24(
1458
2031
  "button",
1459
2032
  {
1460
2033
  "aria-label": "Scroll to top",
1461
2034
  onClick: () => globalThis.scrollTo({ top: 0, behavior: "smooth" }),
1462
2035
  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",
1463
- children: /* @__PURE__ */ jsx20(ArrowUp, { className: "h-5 w-5" })
2036
+ children: /* @__PURE__ */ jsx24(ArrowUp, { className: "h-5 w-5" })
1464
2037
  }
1465
2038
  );
1466
2039
  }
1467
2040
 
1468
2041
  // src/CommentsSection.tsx
1469
2042
  import { useSession, signIn } from "next-auth/react";
1470
- import { useCallback as useCallback2, useEffect as useEffect5, useState as useState8 } from "react";
2043
+ import { useCallback as useCallback2, useEffect as useEffect6, useState as useState8 } from "react";
1471
2044
 
1472
2045
  // src/CommentForm.tsx
1473
2046
  import { useState as useState6 } from "react";
1474
- import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
2047
+ import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
1475
2048
  var MAX_LENGTH = 2e3;
1476
2049
  var WARN_THRESHOLD = 1800;
1477
2050
  function submitLabel(submitting, parentId) {
@@ -1510,8 +2083,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1510
2083
  }
1511
2084
  });
1512
2085
  }
1513
- return /* @__PURE__ */ jsxs20("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
1514
- /* @__PURE__ */ jsx21(
2086
+ return /* @__PURE__ */ jsxs23("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
2087
+ /* @__PURE__ */ jsx25(
1515
2088
  "textarea",
1516
2089
  {
1517
2090
  value: body,
@@ -1524,13 +2097,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1524
2097
  "aria-label": parentId ? "Reply text" : "Comment text"
1525
2098
  }
1526
2099
  ),
1527
- remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ jsxs20("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
2100
+ remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ jsxs23("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
1528
2101
  remaining,
1529
2102
  " characters remaining"
1530
2103
  ] }),
1531
- error && /* @__PURE__ */ jsx21("p", { className: "text-xs text-destructive", children: error }),
1532
- /* @__PURE__ */ jsxs20("div", { className: "flex gap-2", children: [
1533
- /* @__PURE__ */ jsx21(
2104
+ error && /* @__PURE__ */ jsx25("p", { className: "text-xs text-destructive", children: error }),
2105
+ /* @__PURE__ */ jsxs23("div", { className: "flex gap-2", children: [
2106
+ /* @__PURE__ */ jsx25(
1534
2107
  "button",
1535
2108
  {
1536
2109
  type: "submit",
@@ -1539,7 +2112,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1539
2112
  children: submitLabel(submitting, parentId)
1540
2113
  }
1541
2114
  ),
1542
- onCancel && /* @__PURE__ */ jsx21(
2115
+ onCancel && /* @__PURE__ */ jsx25(
1543
2116
  "button",
1544
2117
  {
1545
2118
  type: "button",
@@ -1554,7 +2127,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1554
2127
 
1555
2128
  // src/CommentItem.tsx
1556
2129
  import { useState as useState7 } from "react";
1557
- import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
2130
+ import { Fragment as Fragment5, jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
1558
2131
  function relativeTime(iso) {
1559
2132
  const diff = Date.now() - new Date(iso).getTime();
1560
2133
  const minutes = Math.floor(diff / 6e4);
@@ -1592,15 +2165,15 @@ function CommentItem({
1592
2165
  }
1593
2166
  });
1594
2167
  }
1595
- return /* @__PURE__ */ jsxs21("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
1596
- /* @__PURE__ */ jsx22("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ jsx22("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ jsxs21(Fragment4, { children: [
1597
- /* @__PURE__ */ jsxs21("div", { className: "flex items-center justify-between gap-2", children: [
1598
- /* @__PURE__ */ jsx22("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
1599
- /* @__PURE__ */ jsx22("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
2168
+ return /* @__PURE__ */ jsxs24("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
2169
+ /* @__PURE__ */ jsx26("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ jsx26("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ jsxs24(Fragment5, { children: [
2170
+ /* @__PURE__ */ jsxs24("div", { className: "flex items-center justify-between gap-2", children: [
2171
+ /* @__PURE__ */ jsx26("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
2172
+ /* @__PURE__ */ jsx26("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
1600
2173
  ] }),
1601
- /* @__PURE__ */ jsx22("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
1602
- /* @__PURE__ */ jsxs21("div", { className: "flex gap-3 pt-1", children: [
1603
- canReply && /* @__PURE__ */ jsx22(
2174
+ /* @__PURE__ */ jsx26("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
2175
+ /* @__PURE__ */ jsxs24("div", { className: "flex gap-3 pt-1", children: [
2176
+ canReply && /* @__PURE__ */ jsx26(
1604
2177
  "button",
1605
2178
  {
1606
2179
  onClick: () => setShowReplyForm((v) => !v),
@@ -1608,7 +2181,7 @@ function CommentItem({
1608
2181
  children: showReplyForm ? "Cancel" : "Reply"
1609
2182
  }
1610
2183
  ),
1611
- canDelete && /* @__PURE__ */ jsx22(
2184
+ canDelete && /* @__PURE__ */ jsx26(
1612
2185
  "button",
1613
2186
  {
1614
2187
  onClick: handleDelete,
@@ -1619,7 +2192,7 @@ function CommentItem({
1619
2192
  )
1620
2193
  ] })
1621
2194
  ] }) }),
1622
- showReplyForm && /* @__PURE__ */ jsx22("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ jsx22(
2195
+ showReplyForm && /* @__PURE__ */ jsx26("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ jsx26(
1623
2196
  CommentForm,
1624
2197
  {
1625
2198
  articleSlug,
@@ -1631,7 +2204,7 @@ function CommentItem({
1631
2204
  onCancel: () => setShowReplyForm(false)
1632
2205
  }
1633
2206
  ) }),
1634
- replies.length > 0 && /* @__PURE__ */ jsx22("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ jsx22(
2207
+ replies.length > 0 && /* @__PURE__ */ jsx26("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ jsx26(
1635
2208
  CommentItem,
1636
2209
  {
1637
2210
  comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
@@ -1646,7 +2219,7 @@ function CommentItem({
1646
2219
  }
1647
2220
 
1648
2221
  // src/CommentThread.tsx
1649
- import { jsx as jsx23 } from "react/jsx-runtime";
2222
+ import { jsx as jsx27 } from "react/jsx-runtime";
1650
2223
  function CommentThread({
1651
2224
  comments,
1652
2225
  articleSlug,
@@ -1654,9 +2227,9 @@ function CommentThread({
1654
2227
  onRefresh
1655
2228
  }) {
1656
2229
  if (comments.length === 0) {
1657
- return /* @__PURE__ */ jsx23("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
2230
+ return /* @__PURE__ */ jsx27("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
1658
2231
  }
1659
- return /* @__PURE__ */ jsx23("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ jsx23(
2232
+ return /* @__PURE__ */ jsx27("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ jsx27(
1660
2233
  CommentItem,
1661
2234
  {
1662
2235
  comment,
@@ -1669,7 +2242,7 @@ function CommentThread({
1669
2242
  }
1670
2243
 
1671
2244
  // src/CommentsSection.tsx
1672
- import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
2245
+ import { jsx as jsx28, jsxs as jsxs25 } from "react/jsx-runtime";
1673
2246
  function CommentsSection({ articleSlug, config }) {
1674
2247
  var _a, _b, _c, _d, _e, _f;
1675
2248
  const { data: session, status } = useSession();
@@ -1693,7 +2266,7 @@ function CommentsSection({ articleSlug, config }) {
1693
2266
  setLoading(false);
1694
2267
  }
1695
2268
  }), [articleSlug]);
1696
- useEffect5(() => {
2269
+ useEffect6(() => {
1697
2270
  if (enabled) {
1698
2271
  fetchComments().catch(() => void 0);
1699
2272
  }
@@ -1701,10 +2274,10 @@ function CommentsSection({ articleSlug, config }) {
1701
2274
  if (!enabled) return null;
1702
2275
  const count = comments.length;
1703
2276
  const label = count === 1 ? "1 comment" : `${count} comments`;
1704
- return /* @__PURE__ */ jsxs22("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
1705
- /* @__PURE__ */ jsx24("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
1706
- status === "authenticated" ? /* @__PURE__ */ jsx24(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ jsxs22("p", { className: "text-sm text-muted-foreground", children: [
1707
- /* @__PURE__ */ jsx24(
2277
+ return /* @__PURE__ */ jsxs25("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
2278
+ /* @__PURE__ */ jsx28("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
2279
+ status === "authenticated" ? /* @__PURE__ */ jsx28(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ jsxs25("p", { className: "text-sm text-muted-foreground", children: [
2280
+ /* @__PURE__ */ jsx28(
1708
2281
  "button",
1709
2282
  {
1710
2283
  onClick: () => signIn(),
@@ -1715,8 +2288,8 @@ function CommentsSection({ articleSlug, config }) {
1715
2288
  " ",
1716
2289
  "to join the discussion."
1717
2290
  ] }),
1718
- fetchError && /* @__PURE__ */ jsx24("p", { className: "text-sm text-destructive", children: fetchError }),
1719
- loading ? /* @__PURE__ */ jsx24("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ jsx24(
2291
+ fetchError && /* @__PURE__ */ jsx28("p", { className: "text-sm text-destructive", children: fetchError }),
2292
+ loading ? /* @__PURE__ */ jsx28("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ jsx28(
1720
2293
  CommentThread,
1721
2294
  {
1722
2295
  comments,
@@ -1734,10 +2307,10 @@ export {
1734
2307
  ArticleDetailHero,
1735
2308
  ArticleNavigation,
1736
2309
  ArticleSEO,
1737
- ArticleSchema,
1738
2310
  ArticleSearchBar,
1739
2311
  ArticleSocialShare,
1740
2312
  ArticleTOC,
2313
+ ArticleViewTracker,
1741
2314
  ArticlesHero,
1742
2315
  ArticlesPage,
1743
2316
  AuthorArticlesPage,
@@ -1752,6 +2325,7 @@ export {
1752
2325
  CommentItem,
1753
2326
  CommentThread,
1754
2327
  CommentsSection,
2328
+ CtaViewTracker,
1755
2329
  DEFAULT_CATEGORIES_PAGE_SIZE,
1756
2330
  DEFAULT_LAYOUT,
1757
2331
  DEFAULT_PAGE_SIZE,
@@ -1759,7 +2333,10 @@ export {
1759
2333
  FeaturedArticle,
1760
2334
  LatestArticles,
1761
2335
  LatestArticlesSection,
2336
+ PaginationNav,
2337
+ RelatedArticlesSection,
1762
2338
  ScrollToTop,
2339
+ SeriesArticlesPage,
1763
2340
  breadcrumbsAreEnabled,
1764
2341
  getBreadcrumbsConfig,
1765
2342
  useArticles