@fullstackdatasolutions/articles 0.12.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +550 -3
  3. package/dist/index.cjs +960 -383
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +372 -20
  6. package/dist/index.d.ts +372 -20
  7. package/dist/index.js +944 -371
  8. package/dist/index.js.map +1 -1
  9. package/dist/nextjs.cjs +74 -6
  10. package/dist/nextjs.cjs.map +1 -1
  11. package/dist/nextjs.d.cts +141 -0
  12. package/dist/nextjs.d.ts +141 -0
  13. package/dist/nextjs.js +74 -6
  14. package/dist/nextjs.js.map +1 -1
  15. package/dist/server.cjs +665 -27
  16. package/dist/server.cjs.map +1 -1
  17. package/dist/server.d.cts +349 -3
  18. package/dist/server.d.ts +349 -3
  19. package/dist/server.js +643 -29
  20. package/dist/server.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/ArticleCard.tsx +37 -1
  23. package/src/ArticleContent.tsx +144 -5
  24. package/src/ArticleDetailHero.tsx +23 -0
  25. package/src/ArticleNavigation.tsx +32 -1
  26. package/src/ArticleSchemas.tsx +43 -39
  27. package/src/ArticleSocialShare.tsx +54 -10
  28. package/src/ArticlesPage.tsx +55 -5
  29. package/src/AuthorArticlesPage.tsx +308 -14
  30. package/src/AuthorCard.tsx +1 -1
  31. package/src/CategoryArticlesPage.tsx +34 -2
  32. package/src/LatestArticles.tsx +28 -1
  33. package/src/LatestArticlesSection.tsx +15 -1
  34. package/src/PaginationNav.tsx +78 -0
  35. package/src/RelatedArticlesSection.tsx +55 -0
  36. package/src/SeriesArticlesPage.tsx +66 -0
  37. package/src/__tests__/ArticleCard.test.tsx +63 -3
  38. package/src/__tests__/ArticleContent.test.tsx +143 -0
  39. package/src/__tests__/ArticleDetailHero.test.tsx +30 -0
  40. package/src/__tests__/ArticleNavigation.test.tsx +81 -3
  41. package/src/__tests__/ArticleSchemas.test.tsx +155 -81
  42. package/src/__tests__/ArticleSocialShare.test.tsx +54 -0
  43. package/src/__tests__/ArticlesPage.test.tsx +131 -0
  44. package/src/__tests__/AuthorArticlesPage.test.tsx +304 -3
  45. package/src/__tests__/CategoryArticlesPage.test.tsx +116 -1
  46. package/src/__tests__/LatestArticles.test.tsx +52 -0
  47. package/src/__tests__/LatestArticlesSection.test.tsx +28 -0
  48. package/src/__tests__/PaginationNav.test.tsx +73 -0
  49. package/src/__tests__/RelatedArticlesSection.test.tsx +132 -0
  50. package/src/__tests__/SeriesArticlesPage.test.tsx +121 -0
  51. package/src/__tests__/eventTracking.test.tsx +145 -0
  52. package/src/__tests__/events.test.ts +82 -0
  53. package/src/__tests__/markdown.test.ts +78 -1
  54. package/src/__tests__/pagination.test.ts +178 -0
  55. package/src/__tests__/seoUtils-authors.test.ts +37 -0
  56. package/src/__tests__/seoUtils.test.ts +246 -0
  57. package/src/__tests__/server-articles.test.ts +356 -1
  58. package/src/__tests__/validateArticles.test.ts +312 -0
  59. package/src/articleTypes.ts +109 -0
  60. package/src/articlesConfig.ts +37 -1
  61. package/src/eventTracking.tsx +97 -0
  62. package/src/events.ts +105 -0
  63. package/src/index.ts +26 -1
  64. package/src/markdown.ts +41 -0
  65. package/src/pagination.ts +93 -0
  66. package/src/seoUtils.ts +198 -11
  67. package/src/server-articles.ts +199 -6
  68. package/src/server.ts +46 -2
  69. package/src/validateArticles.ts +260 -0
package/dist/index.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
 
@@ -715,7 +927,7 @@ function useArticles(initialArticles, initialCategories) {
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,
@@ -825,7 +1039,10 @@ function renderSection(section, ctx, config) {
825
1039
  function ArticlesPage({
826
1040
  config,
827
1041
  initialArticles,
828
- initialCategories
1042
+ initialCategories,
1043
+ page,
1044
+ totalPages,
1045
+ totalCount
829
1046
  }) {
830
1047
  var _a, _b, _c;
831
1048
  const state = useArticles(initialArticles, initialCategories);
@@ -833,19 +1050,34 @@ function ArticlesPage({
833
1050
  const pageSize = (_b = config.pageSize) != null ? _b : DEFAULT_PAGE_SIZE;
834
1051
  const categoriesPageSize = (_c = config.categoriesPageSize) != null ? _c : DEFAULT_CATEGORIES_PAGE_SIZE;
835
1052
  const themeVars = buildThemeVars(config.theme);
836
- 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");
837
1057
  const articlesWithoutFeatured = hasFeatured ? state.articles.slice(1) : state.articles;
838
1058
  const displayedArticles = state.searchQuery ? state.articles : articlesWithoutFeatured;
839
- const ctx = __spreadProps(__spreadValues({}, state), { displayedArticles, pageSize, categoriesPageSize });
840
- return /* @__PURE__ */ jsxs10("div", { style: themeVars, children: [
841
- layout.map((section) => renderSection(section, ctx, config)),
842
- 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(
843
1070
  CollectionPageSchema,
844
1071
  {
845
1072
  title: `Articles | ${config.siteName}`,
846
- description: `Browse all ${state.articles.length} articles on ${config.siteName}.`,
1073
+ description: `Browse all ${articleCount} articles on ${config.siteName}.`,
847
1074
  url: `${config.siteUrl}/articles`,
848
- 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
+ }))
849
1081
  }
850
1082
  )
851
1083
  ] });
@@ -853,10 +1085,10 @@ function ArticlesPage({
853
1085
 
854
1086
  // src/ArticleDetailHero.tsx
855
1087
  import { Fragment as Fragment3 } from "react";
856
- import Image4 from "next/image";
857
- import Link6 from "next/link";
1088
+ import Image5 from "next/image";
1089
+ import Link8 from "next/link";
858
1090
  import { Calendar as Calendar3, Clock as Clock3, User as User2 } from "lucide-react";
859
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1091
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
860
1092
  function toSlug(cat) {
861
1093
  return cat.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
862
1094
  }
@@ -869,7 +1101,7 @@ function ArticleDetailHero({
869
1101
  }) {
870
1102
  const showConfiguredAuthors = showAuthor && authors.length > 0;
871
1103
  const showLegacyAuthor = showAuthor && authors.length === 0 && article.author.trim().length > 0;
872
- return /* @__PURE__ */ jsxs11(
1104
+ return /* @__PURE__ */ jsxs13(
873
1105
  "section",
874
1106
  {
875
1107
  style: {
@@ -881,8 +1113,8 @@ function ArticleDetailHero({
881
1113
  overflow: "hidden"
882
1114
  },
883
1115
  children: [
884
- /* @__PURE__ */ jsx11(
885
- Image4,
1116
+ /* @__PURE__ */ jsx13(
1117
+ Image5,
886
1118
  {
887
1119
  src: article.featuredImage,
888
1120
  alt: article.title,
@@ -893,7 +1125,7 @@ function ArticleDetailHero({
893
1125
  priority: true
894
1126
  }
895
1127
  ),
896
- /* @__PURE__ */ jsx11(
1128
+ /* @__PURE__ */ jsx13(
897
1129
  "div",
898
1130
  {
899
1131
  style: {
@@ -903,7 +1135,7 @@ function ArticleDetailHero({
903
1135
  }
904
1136
  }
905
1137
  ),
906
- /* @__PURE__ */ jsxs11(
1138
+ /* @__PURE__ */ jsxs13(
907
1139
  "div",
908
1140
  {
909
1141
  style: {
@@ -917,7 +1149,7 @@ function ArticleDetailHero({
917
1149
  width: "100%"
918
1150
  },
919
1151
  children: [
920
- article.categories && article.categories.length > 0 && /* @__PURE__ */ jsx11(
1152
+ article.categories && article.categories.length > 0 && /* @__PURE__ */ jsx13(
921
1153
  "div",
922
1154
  {
923
1155
  style: {
@@ -928,15 +1160,15 @@ function ArticleDetailHero({
928
1160
  marginBottom: "1rem"
929
1161
  },
930
1162
  children: article.categories.slice(0, 4).map(
931
- (cat) => categoryBasePath ? /* @__PURE__ */ jsx11(
932
- Link6,
1163
+ (cat) => categoryBasePath ? /* @__PURE__ */ jsx13(
1164
+ Link8,
933
1165
  {
934
1166
  href: `${categoryBasePath}/${toSlug(cat)}`,
935
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",
936
1168
  children: cat
937
1169
  },
938
1170
  cat
939
- ) : /* @__PURE__ */ jsx11(
1171
+ ) : /* @__PURE__ */ jsx13(
940
1172
  "span",
941
1173
  {
942
1174
  className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full",
@@ -947,8 +1179,8 @@ function ArticleDetailHero({
947
1179
  )
948
1180
  }
949
1181
  ),
950
- /* @__PURE__ */ jsx11("h1", { className: "text-4xl md:text-5xl font-bold mb-4", children: article.title }),
951
- /* @__PURE__ */ jsxs11(
1182
+ /* @__PURE__ */ jsx13("h1", { className: "text-4xl md:text-5xl font-bold mb-4", children: article.title }),
1183
+ /* @__PURE__ */ jsxs13(
952
1184
  "div",
953
1185
  {
954
1186
  style: {
@@ -960,23 +1192,23 @@ function ArticleDetailHero({
960
1192
  color: "rgba(255,255,255,0.8)"
961
1193
  },
962
1194
  children: [
963
- showDate && article.date && /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
964
- /* @__PURE__ */ jsx11(Calendar3, { className: "h-4 w-4" }),
965
- /* @__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 })
966
1198
  ] }),
967
- /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
968
- /* @__PURE__ */ jsx11(Clock3, { className: "h-4 w-4" }),
969
- /* @__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 })
970
1202
  ] }),
971
- showConfiguredAuthors && /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
972
- /* @__PURE__ */ jsx11(User2, { className: "h-4 w-4" }),
973
- /* @__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: [
974
1206
  "By",
975
1207
  " ",
976
- authors.map((author, index) => /* @__PURE__ */ jsxs11(Fragment3, { children: [
1208
+ authors.map((author, index) => /* @__PURE__ */ jsxs13(Fragment3, { children: [
977
1209
  index > 0 && ", ",
978
- /* @__PURE__ */ jsx11(
979
- Link6,
1210
+ /* @__PURE__ */ jsx13(
1211
+ Link8,
980
1212
  {
981
1213
  href: `/articles/authors/${author.slug}`,
982
1214
  className: "font-medium text-white underline-offset-4 hover:underline",
@@ -986,15 +1218,26 @@ function ArticleDetailHero({
986
1218
  ] }, author.slug))
987
1219
  ] })
988
1220
  ] }),
989
- showLegacyAuthor && /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
990
- /* @__PURE__ */ jsx11(User2, { className: "h-4 w-4" }),
991
- /* @__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: [
992
1224
  "By ",
993
1225
  article.author
994
1226
  ] })
995
1227
  ] })
996
1228
  ]
997
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
+ }
998
1241
  )
999
1242
  ]
1000
1243
  }
@@ -1005,9 +1248,9 @@ function ArticleDetailHero({
1005
1248
  }
1006
1249
 
1007
1250
  // src/CategoryArticlesPage.tsx
1008
- import Image5 from "next/image";
1009
- import Link7 from "next/link";
1010
- 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";
1011
1254
  function getCategoryDescription(config, slug, categoryName) {
1012
1255
  var _a;
1013
1256
  const raw = (_a = config.categoryDescriptions) == null ? void 0 : _a[slug];
@@ -1049,7 +1292,14 @@ function buildCategoryBreadcrumbEntry(entry, context) {
1049
1292
  }
1050
1293
  return [{ name: context.categoryName }];
1051
1294
  }
1052
- function CategoryArticlesPage({ category, articles, config }) {
1295
+ function CategoryArticlesPage({
1296
+ category,
1297
+ articles,
1298
+ config,
1299
+ page,
1300
+ totalPages,
1301
+ totalCount
1302
+ }) {
1053
1303
  var _a;
1054
1304
  if (articles.length === 0) return null;
1055
1305
  const categoryName = articles[0].category;
@@ -1058,8 +1308,25 @@ function CategoryArticlesPage({ category, articles, config }) {
1058
1308
  const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1059
1309
  const breadcrumbConfig = getBreadcrumbsConfig(config);
1060
1310
  const breadcrumbItems = buildCategoryBreadcrumbItems(config, categoryName);
1061
- return /* @__PURE__ */ jsxs12("div", { children: [
1062
- 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(
1063
1330
  Breadcrumb,
1064
1331
  {
1065
1332
  items: breadcrumbItems,
@@ -1067,7 +1334,7 @@ function CategoryArticlesPage({ category, articles, config }) {
1067
1334
  showSchema: breadcrumbConfig.showSchema !== false
1068
1335
  }
1069
1336
  ),
1070
- /* @__PURE__ */ jsxs12(
1337
+ /* @__PURE__ */ jsxs14(
1071
1338
  "section",
1072
1339
  {
1073
1340
  style: {
@@ -1079,8 +1346,8 @@ function CategoryArticlesPage({ category, articles, config }) {
1079
1346
  overflow: "hidden"
1080
1347
  },
1081
1348
  children: [
1082
- /* @__PURE__ */ jsx12(
1083
- Image5,
1349
+ /* @__PURE__ */ jsx14(
1350
+ Image6,
1084
1351
  {
1085
1352
  src: heroImage,
1086
1353
  alt: categoryName,
@@ -1090,8 +1357,8 @@ function CategoryArticlesPage({ category, articles, config }) {
1090
1357
  priority: true
1091
1358
  }
1092
1359
  ),
1093
- /* @__PURE__ */ jsx12("div", { style: { position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.6)" } }),
1094
- /* @__PURE__ */ jsxs12(
1360
+ /* @__PURE__ */ jsx14("div", { style: { position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.6)" } }),
1361
+ /* @__PURE__ */ jsxs14(
1095
1362
  "div",
1096
1363
  {
1097
1364
  style: {
@@ -1102,10 +1369,10 @@ function CategoryArticlesPage({ category, articles, config }) {
1102
1369
  padding: "0 1rem"
1103
1370
  },
1104
1371
  children: [
1105
- /* @__PURE__ */ jsx12("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
1106
- /* @__PURE__ */ jsx12("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
1107
- description.long && /* @__PURE__ */ jsx12("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
1108
- /* @__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: [
1109
1376
  articles.length,
1110
1377
  " article",
1111
1378
  articles.length === 1 ? "" : "s"
@@ -1116,150 +1383,75 @@ function CategoryArticlesPage({ category, articles, config }) {
1116
1383
  ]
1117
1384
  }
1118
1385
  ),
1119
- /* @__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: [
1120
- /* @__PURE__ */ jsxs12("div", { className: "mb-8 flex items-center justify-between", children: [
1121
- /* @__PURE__ */ jsx12(Link7, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
1122
- /* @__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 })
1123
1390
  ] }),
1124
- /* @__PURE__ */ jsx12(LatestArticles, { articles, pageSize })
1391
+ /* @__PURE__ */ jsx14(LatestArticles, { articles, pageSize, pagination })
1125
1392
  ] }) })
1126
1393
  ] });
1127
1394
  }
1128
1395
 
1129
- // src/AuthorArticlesPage.tsx
1130
- import Link8 from "next/link";
1131
- import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1132
- function getPersonSchema(author, config, articleCount) {
1133
- var _a, _b;
1134
- return __spreadProps(__spreadValues(__spreadValues({
1135
- "@context": "https://schema.org",
1136
- "@type": "Person",
1137
- name: author.name,
1138
- description: author.bio,
1139
- url: (_a = author.url) != null ? _a : getAuthorUrl(author)
1140
- }, getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }), {
1141
- knowsAbout: config.siteName,
1142
- mainEntityOfPage: (_b = author.url) != null ? _b : `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`,
1143
- interactionStatistic: {
1144
- "@type": "InteractionCounter",
1145
- interactionType: "https://schema.org/WriteAction",
1146
- userInteractionCount: articleCount
1147
- }
1148
- });
1149
- }
1150
- 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 }) {
1151
1400
  var _a;
1152
- const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1153
- return /* @__PURE__ */ jsxs13("div", { children: [
1154
- /* @__PURE__ */ jsx13(
1155
- "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,
1156
1408
  {
1157
- type: "application/ld+json",
1158
- dangerouslySetInnerHTML: {
1159
- __html: JSON.stringify(getPersonSchema(author, config, articles.length))
1160
- }
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
+ }))
1161
1418
  }
1162
1419
  ),
1163
- /* @__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: [
1164
- /* @__PURE__ */ jsxs13("div", { className: "mb-8 flex items-center justify-between gap-4", children: [
1165
- /* @__PURE__ */ jsx13(Link8, { href: "/articles", className: "text-sm text-primary hover:underline", children: "All Articles" }),
1166
- /* @__PURE__ */ jsxs13("p", { className: "text-sm text-muted-foreground", children: [
1167
- "Articles by ",
1168
- 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"
1169
1428
  ] })
1170
1429
  ] }),
1171
- /* @__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
+ )
1172
1438
  ] }) })
1173
1439
  ] });
1174
1440
  }
1175
1441
 
1176
- // src/AuthorCard.tsx
1177
- import Image6 from "next/image";
1178
- import Link9 from "next/link";
1179
- import {
1180
- Facebook,
1181
- Github,
1182
- Globe,
1183
- Instagram,
1184
- Linkedin,
1185
- Mail,
1186
- MessageCircle,
1187
- Twitter,
1188
- Youtube
1189
- } from "lucide-react";
1190
- import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1191
- function getInitials(name) {
1192
- return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
1193
- }
1194
- function AuthorCard({
1195
- author,
1196
- linkToPage = true,
1197
- showBio = false,
1198
- showSocial = false
1199
- }) {
1200
- const avatar = getAuthorAvatar(author);
1201
- 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 });
1202
- return /* @__PURE__ */ jsxs14("div", { className: "flex items-start gap-3 rounded-lg border border-border bg-card p-4 text-card-foreground", children: [
1203
- avatar ? /* @__PURE__ */ jsx14(
1204
- Image6,
1205
- {
1206
- src: avatar,
1207
- alt: author.name,
1208
- width: 48,
1209
- height: 48,
1210
- className: "h-12 w-12 rounded-full object-cover"
1211
- }
1212
- ) : /* @__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) }),
1213
- /* @__PURE__ */ jsxs14("div", { className: "min-w-0 flex-1", children: [
1214
- name,
1215
- showBio && author.bio && /* @__PURE__ */ jsx14("p", { className: "mt-1 line-clamp-2 text-sm text-muted-foreground", children: author.bio }),
1216
- showSocial && /* @__PURE__ */ jsx14(AuthorSocialLinks, { author })
1217
- ] })
1218
- ] });
1219
- }
1220
- var SOCIAL_ICONS = {
1221
- Website: Globe,
1222
- Facebook,
1223
- Twitter,
1224
- X: Twitter,
1225
- LinkedIn: Linkedin,
1226
- Instagram,
1227
- YouTube: Youtube,
1228
- GitHub: Github,
1229
- Newsletter: Mail
1230
- };
1231
- function getSocialIcon(label) {
1232
- var _a;
1233
- return (_a = SOCIAL_ICONS[label]) != null ? _a : MessageCircle;
1234
- }
1235
- function AuthorSocialLinks({ author }) {
1236
- const links = getAuthorSocialLinks(author);
1237
- if (links.length === 0) return null;
1238
- return /* @__PURE__ */ jsx14("div", { className: "mt-3 flex items-center gap-2", children: links.map(({ href, label }) => {
1239
- const Icon = getSocialIcon(label);
1240
- return /* @__PURE__ */ jsx14(
1241
- Link9,
1242
- {
1243
- href,
1244
- 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",
1245
- "aria-label": `${author.name} on ${label}`,
1246
- children: /* @__PURE__ */ jsx14(Icon, { className: "h-4 w-4" })
1247
- },
1248
- `${label}-${href}`
1249
- );
1250
- }) });
1251
- }
1442
+ // src/AuthorArticlesPage.tsx
1443
+ import Link11 from "next/link";
1252
1444
 
1253
1445
  // src/AuthorDetailHero.tsx
1254
1446
  import Image7 from "next/image";
1255
- import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1447
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1256
1448
  function getInitials2(name) {
1257
1449
  return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
1258
1450
  }
1259
1451
  function AuthorDetailHero({ author, articleCount }) {
1260
1452
  const avatar = getAuthorAvatar(author);
1261
- 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: [
1262
- 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(
1263
1455
  Image7,
1264
1456
  {
1265
1457
  src: avatar,
@@ -1269,27 +1461,303 @@ function AuthorDetailHero({ author, articleCount }) {
1269
1461
  className: "h-28 w-28 rounded-full object-cover",
1270
1462
  priority: true
1271
1463
  }
1272
- ) : /* @__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) }),
1273
- /* @__PURE__ */ jsxs15("div", { children: [
1274
- /* @__PURE__ */ jsx15("p", { className: "mb-3 text-sm font-semibold uppercase tracking-widest text-muted-foreground", children: "Author" }),
1275
- /* @__PURE__ */ jsx15("h1", { className: "text-4xl font-bold text-foreground md:text-5xl", children: author.name }),
1276
- /* @__PURE__ */ jsx15("p", { className: "mx-auto mt-4 max-w-2xl text-lg text-muted-foreground", children: author.bio }),
1277
- 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: [
1278
1470
  articleCount,
1279
1471
  " article",
1280
1472
  articleCount === 1 ? "" : "s"
1281
1473
  ] })
1282
1474
  ] }),
1283
- /* @__PURE__ */ jsx15(AuthorSocialLinks, { author })
1475
+ /* @__PURE__ */ jsx16(AuthorSocialLinks, { author })
1284
1476
  ] }) });
1285
1477
  }
1286
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
+
1287
1744
  // src/ArticleSocialShare.tsx
1288
1745
  import { useState as useState4 } from "react";
1289
1746
  import { Share2, Copy, Check, Mail as Mail2, MessageCircle as MessageCircle2, Users, FileText } from "lucide-react";
1290
- import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1291
- 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
+ }) {
1292
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
+ }
1293
1761
  const encodedTitle = encodeURIComponent(title);
1294
1762
  const encodedUrl = encodeURIComponent(url);
1295
1763
  const encodedExcerpt = encodeURIComponent(excerpt || "");
@@ -1306,102 +1774,203 @@ function ArticleSocialShare({ title, url, excerpt, shareMessage }) {
1306
1774
  try {
1307
1775
  yield navigator.clipboard.writeText(url);
1308
1776
  setCopied(true);
1777
+ trackShare("copy-link");
1309
1778
  setTimeout(() => setCopied(false), 2e3);
1310
1779
  } catch (e) {
1311
1780
  }
1312
1781
  });
1313
- const openShareWindow = (shareUrl) => {
1782
+ const openShareWindow = (shareUrl, channel) => {
1783
+ trackShare(channel);
1314
1784
  globalThis.open(shareUrl, "_blank", "width=600,height=400,scrollbars=yes,resizable=yes");
1315
1785
  };
1316
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";
1317
- return /* @__PURE__ */ jsxs16("div", { className: "border-t border-border pt-8 mt-8", children: [
1318
- /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 mb-4", children: [
1319
- /* @__PURE__ */ jsx16(Share2, { className: "h-5 w-5 text-muted-foreground" }),
1320
- /* @__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" })
1321
1791
  ] }),
1322
- /* @__PURE__ */ jsxs16("div", { className: "flex flex-wrap gap-2", children: [
1323
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.linkedin), children: [
1324
- /* @__PURE__ */ jsx16(Users, { className: "h-4 w-4" }),
1325
- "LinkedIn"
1326
- ] }),
1327
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.facebook), children: [
1328
- /* @__PURE__ */ jsx16("span", { className: "text-xs font-bold", children: "f" }),
1329
- " Facebook"
1330
- ] }),
1331
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.twitter), children: [
1332
- /* @__PURE__ */ jsx16(MessageCircle2, { className: "h-4 w-4" }),
1333
- "Twitter"
1334
- ] }),
1335
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.reddit), children: [
1336
- /* @__PURE__ */ jsx16(FileText, { className: "h-4 w-4" }),
1337
- "Reddit"
1338
- ] }),
1339
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.whatsapp), children: [
1340
- /* @__PURE__ */ jsx16(MessageCircle2, { className: "h-4 w-4" }),
1341
- "WhatsApp"
1342
- ] }),
1343
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.telegram), children: [
1344
- /* @__PURE__ */ jsx16(MessageCircle2, { className: "h-4 w-4" }),
1345
- "Telegram"
1346
- ] }),
1347
- /* @__PURE__ */ jsxs16("a", { className: btnClass, href: shareLinks.email, children: [
1348
- /* @__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" }),
1349
1867
  "Email"
1350
1868
  ] }),
1351
- /* @__PURE__ */ jsxs16("button", { className: btnClass, onClick: copyToClipboard, children: [
1352
- 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" }),
1353
1871
  copied ? "Copied!" : "Copy Link"
1354
1872
  ] })
1355
1873
  ] }),
1356
- 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 })
1357
1875
  ] });
1358
1876
  }
1359
1877
 
1360
1878
  // src/ArticleNavigation.tsx
1361
- import Link10 from "next/link";
1362
- import { ChevronLeft, ChevronRight } from "lucide-react";
1363
- 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";
1364
1882
  function truncateTitle(title, maxLength = 60) {
1365
1883
  return title.length > maxLength ? `${title.substring(0, maxLength)}...` : title;
1366
1884
  }
1367
- function ArticleNavigation({ previous, next, basePath }) {
1885
+ function ArticleNavigation({
1886
+ previous,
1887
+ next,
1888
+ basePath,
1889
+ fromSlug,
1890
+ pathKey,
1891
+ config
1892
+ }) {
1368
1893
  if (!previous && !next) return null;
1369
- 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: [
1370
- /* @__PURE__ */ jsx17("div", { className: "flex-1 min-w-0", children: previous && /* @__PURE__ */ jsxs17(
1371
- 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,
1372
1907
  {
1373
1908
  href: `${basePath}/${previous.slug}`,
1909
+ onClick: () => emitStep(previous.slug, "previous"),
1374
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",
1375
1911
  children: [
1376
- /* @__PURE__ */ jsx17(ChevronLeft, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" }),
1377
- /* @__PURE__ */ jsxs17("div", { className: "min-w-0 flex-1", children: [
1378
- /* @__PURE__ */ jsx17("div", { className: "text-sm text-muted-foreground mb-1", children: "Previous Article" }),
1379
- /* @__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) })
1380
1916
  ] })
1381
1917
  ]
1382
1918
  }
1383
1919
  ) }),
1384
- /* @__PURE__ */ jsx17("div", { className: "flex-1 min-w-0", children: next && /* @__PURE__ */ jsxs17(
1385
- Link10,
1920
+ /* @__PURE__ */ jsx20("div", { className: "flex-1 min-w-0", children: next && /* @__PURE__ */ jsxs19(
1921
+ Link12,
1386
1922
  {
1387
1923
  href: `${basePath}/${next.slug}`,
1924
+ onClick: () => emitStep(next.slug, "next"),
1388
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",
1389
1926
  children: [
1390
- /* @__PURE__ */ jsxs17("div", { className: "min-w-0 flex-1 text-right", children: [
1391
- /* @__PURE__ */ jsx17("div", { className: "text-sm text-muted-foreground mb-1", children: "Next Article" }),
1392
- /* @__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) })
1393
1930
  ] }),
1394
- /* @__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" })
1395
1932
  ]
1396
1933
  }
1397
1934
  ) })
1398
1935
  ] }) });
1399
1936
  }
1400
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
+
1401
1970
  // src/ArticleBackLink.tsx
1402
- import Link11 from "next/link";
1971
+ import Link13 from "next/link";
1403
1972
  import { ArrowLeft } from "lucide-react";
1404
- import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
1973
+ import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
1405
1974
  function ArticleBackLink({
1406
1975
  config,
1407
1976
  href = "/articles",
@@ -1409,13 +1978,13 @@ function ArticleBackLink({
1409
1978
  className
1410
1979
  }) {
1411
1980
  if (config.showBackToArticles === false) return null;
1412
- return /* @__PURE__ */ jsxs18(
1413
- Link11,
1981
+ return /* @__PURE__ */ jsxs21(
1982
+ Link13,
1414
1983
  {
1415
1984
  href,
1416
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",
1417
1986
  children: [
1418
- /* @__PURE__ */ jsx18(ArrowLeft, { className: "h-4 w-4" }),
1987
+ /* @__PURE__ */ jsx22(ArrowLeft, { className: "h-4 w-4" }),
1419
1988
  label
1420
1989
  ]
1421
1990
  }
@@ -1423,17 +1992,17 @@ function ArticleBackLink({
1423
1992
  }
1424
1993
 
1425
1994
  // src/ArticleTOC.tsx
1426
- import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
1995
+ import { jsx as jsx23, jsxs as jsxs22 } from "react/jsx-runtime";
1427
1996
  function ArticleTOC({ toc, className }) {
1428
1997
  if (!toc.length) return null;
1429
- return /* @__PURE__ */ jsxs19(
1998
+ return /* @__PURE__ */ jsxs22(
1430
1999
  "nav",
1431
2000
  {
1432
2001
  "aria-label": "Table of contents",
1433
2002
  className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
1434
2003
  children: [
1435
- /* @__PURE__ */ jsx19("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
1436
- /* @__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(
1437
2006
  "a",
1438
2007
  {
1439
2008
  href: `#${item.id}`,
@@ -1447,35 +2016,35 @@ function ArticleTOC({ toc, className }) {
1447
2016
  }
1448
2017
 
1449
2018
  // src/ScrollToTop.tsx
1450
- import { useEffect as useEffect4, useState as useState5 } from "react";
2019
+ import { useEffect as useEffect5, useState as useState5 } from "react";
1451
2020
  import { ArrowUp } from "lucide-react";
1452
- import { jsx as jsx20 } from "react/jsx-runtime";
2021
+ import { jsx as jsx24 } from "react/jsx-runtime";
1453
2022
  function ScrollToTop() {
1454
2023
  const [visible, setVisible] = useState5(false);
1455
- useEffect4(() => {
2024
+ useEffect5(() => {
1456
2025
  const onScroll = () => setVisible(globalThis.scrollY > 300);
1457
2026
  globalThis.addEventListener("scroll", onScroll, { passive: true });
1458
2027
  return () => globalThis.removeEventListener("scroll", onScroll);
1459
2028
  }, []);
1460
2029
  if (!visible) return null;
1461
- return /* @__PURE__ */ jsx20(
2030
+ return /* @__PURE__ */ jsx24(
1462
2031
  "button",
1463
2032
  {
1464
2033
  "aria-label": "Scroll to top",
1465
2034
  onClick: () => globalThis.scrollTo({ top: 0, behavior: "smooth" }),
1466
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",
1467
- children: /* @__PURE__ */ jsx20(ArrowUp, { className: "h-5 w-5" })
2036
+ children: /* @__PURE__ */ jsx24(ArrowUp, { className: "h-5 w-5" })
1468
2037
  }
1469
2038
  );
1470
2039
  }
1471
2040
 
1472
2041
  // src/CommentsSection.tsx
1473
2042
  import { useSession, signIn } from "next-auth/react";
1474
- 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";
1475
2044
 
1476
2045
  // src/CommentForm.tsx
1477
2046
  import { useState as useState6 } from "react";
1478
- import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
2047
+ import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
1479
2048
  var MAX_LENGTH = 2e3;
1480
2049
  var WARN_THRESHOLD = 1800;
1481
2050
  function submitLabel(submitting, parentId) {
@@ -1514,8 +2083,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1514
2083
  }
1515
2084
  });
1516
2085
  }
1517
- return /* @__PURE__ */ jsxs20("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
1518
- /* @__PURE__ */ jsx21(
2086
+ return /* @__PURE__ */ jsxs23("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
2087
+ /* @__PURE__ */ jsx25(
1519
2088
  "textarea",
1520
2089
  {
1521
2090
  value: body,
@@ -1528,13 +2097,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1528
2097
  "aria-label": parentId ? "Reply text" : "Comment text"
1529
2098
  }
1530
2099
  ),
1531
- 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: [
1532
2101
  remaining,
1533
2102
  " characters remaining"
1534
2103
  ] }),
1535
- error && /* @__PURE__ */ jsx21("p", { className: "text-xs text-destructive", children: error }),
1536
- /* @__PURE__ */ jsxs20("div", { className: "flex gap-2", children: [
1537
- /* @__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(
1538
2107
  "button",
1539
2108
  {
1540
2109
  type: "submit",
@@ -1543,7 +2112,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1543
2112
  children: submitLabel(submitting, parentId)
1544
2113
  }
1545
2114
  ),
1546
- onCancel && /* @__PURE__ */ jsx21(
2115
+ onCancel && /* @__PURE__ */ jsx25(
1547
2116
  "button",
1548
2117
  {
1549
2118
  type: "button",
@@ -1558,7 +2127,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1558
2127
 
1559
2128
  // src/CommentItem.tsx
1560
2129
  import { useState as useState7 } from "react";
1561
- 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";
1562
2131
  function relativeTime(iso) {
1563
2132
  const diff = Date.now() - new Date(iso).getTime();
1564
2133
  const minutes = Math.floor(diff / 6e4);
@@ -1596,15 +2165,15 @@ function CommentItem({
1596
2165
  }
1597
2166
  });
1598
2167
  }
1599
- return /* @__PURE__ */ jsxs21("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
1600
- /* @__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: [
1601
- /* @__PURE__ */ jsxs21("div", { className: "flex items-center justify-between gap-2", children: [
1602
- /* @__PURE__ */ jsx22("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
1603
- /* @__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) })
1604
2173
  ] }),
1605
- /* @__PURE__ */ jsx22("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
1606
- /* @__PURE__ */ jsxs21("div", { className: "flex gap-3 pt-1", children: [
1607
- 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(
1608
2177
  "button",
1609
2178
  {
1610
2179
  onClick: () => setShowReplyForm((v) => !v),
@@ -1612,7 +2181,7 @@ function CommentItem({
1612
2181
  children: showReplyForm ? "Cancel" : "Reply"
1613
2182
  }
1614
2183
  ),
1615
- canDelete && /* @__PURE__ */ jsx22(
2184
+ canDelete && /* @__PURE__ */ jsx26(
1616
2185
  "button",
1617
2186
  {
1618
2187
  onClick: handleDelete,
@@ -1623,7 +2192,7 @@ function CommentItem({
1623
2192
  )
1624
2193
  ] })
1625
2194
  ] }) }),
1626
- 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(
1627
2196
  CommentForm,
1628
2197
  {
1629
2198
  articleSlug,
@@ -1635,7 +2204,7 @@ function CommentItem({
1635
2204
  onCancel: () => setShowReplyForm(false)
1636
2205
  }
1637
2206
  ) }),
1638
- 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(
1639
2208
  CommentItem,
1640
2209
  {
1641
2210
  comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
@@ -1650,7 +2219,7 @@ function CommentItem({
1650
2219
  }
1651
2220
 
1652
2221
  // src/CommentThread.tsx
1653
- import { jsx as jsx23 } from "react/jsx-runtime";
2222
+ import { jsx as jsx27 } from "react/jsx-runtime";
1654
2223
  function CommentThread({
1655
2224
  comments,
1656
2225
  articleSlug,
@@ -1658,9 +2227,9 @@ function CommentThread({
1658
2227
  onRefresh
1659
2228
  }) {
1660
2229
  if (comments.length === 0) {
1661
- 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." });
1662
2231
  }
1663
- 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(
1664
2233
  CommentItem,
1665
2234
  {
1666
2235
  comment,
@@ -1673,7 +2242,7 @@ function CommentThread({
1673
2242
  }
1674
2243
 
1675
2244
  // src/CommentsSection.tsx
1676
- import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
2245
+ import { jsx as jsx28, jsxs as jsxs25 } from "react/jsx-runtime";
1677
2246
  function CommentsSection({ articleSlug, config }) {
1678
2247
  var _a, _b, _c, _d, _e, _f;
1679
2248
  const { data: session, status } = useSession();
@@ -1697,7 +2266,7 @@ function CommentsSection({ articleSlug, config }) {
1697
2266
  setLoading(false);
1698
2267
  }
1699
2268
  }), [articleSlug]);
1700
- useEffect5(() => {
2269
+ useEffect6(() => {
1701
2270
  if (enabled) {
1702
2271
  fetchComments().catch(() => void 0);
1703
2272
  }
@@ -1705,10 +2274,10 @@ function CommentsSection({ articleSlug, config }) {
1705
2274
  if (!enabled) return null;
1706
2275
  const count = comments.length;
1707
2276
  const label = count === 1 ? "1 comment" : `${count} comments`;
1708
- return /* @__PURE__ */ jsxs22("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
1709
- /* @__PURE__ */ jsx24("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
1710
- status === "authenticated" ? /* @__PURE__ */ jsx24(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ jsxs22("p", { className: "text-sm text-muted-foreground", children: [
1711
- /* @__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(
1712
2281
  "button",
1713
2282
  {
1714
2283
  onClick: () => signIn(),
@@ -1719,8 +2288,8 @@ function CommentsSection({ articleSlug, config }) {
1719
2288
  " ",
1720
2289
  "to join the discussion."
1721
2290
  ] }),
1722
- fetchError && /* @__PURE__ */ jsx24("p", { className: "text-sm text-destructive", children: fetchError }),
1723
- 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(
1724
2293
  CommentThread,
1725
2294
  {
1726
2295
  comments,
@@ -1738,10 +2307,10 @@ export {
1738
2307
  ArticleDetailHero,
1739
2308
  ArticleNavigation,
1740
2309
  ArticleSEO,
1741
- ArticleSchema,
1742
2310
  ArticleSearchBar,
1743
2311
  ArticleSocialShare,
1744
2312
  ArticleTOC,
2313
+ ArticleViewTracker,
1745
2314
  ArticlesHero,
1746
2315
  ArticlesPage,
1747
2316
  AuthorArticlesPage,
@@ -1756,6 +2325,7 @@ export {
1756
2325
  CommentItem,
1757
2326
  CommentThread,
1758
2327
  CommentsSection,
2328
+ CtaViewTracker,
1759
2329
  DEFAULT_CATEGORIES_PAGE_SIZE,
1760
2330
  DEFAULT_LAYOUT,
1761
2331
  DEFAULT_PAGE_SIZE,
@@ -1763,7 +2333,10 @@ export {
1763
2333
  FeaturedArticle,
1764
2334
  LatestArticles,
1765
2335
  LatestArticlesSection,
2336
+ PaginationNav,
2337
+ RelatedArticlesSection,
1766
2338
  ScrollToTop,
2339
+ SeriesArticlesPage,
1767
2340
  breadcrumbsAreEnabled,
1768
2341
  getBreadcrumbsConfig,
1769
2342
  useArticles