@fullstackdatasolutions/articles 0.12.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +559 -11
  3. package/dist/index.cjs +984 -388
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +383 -21
  6. package/dist/index.d.ts +383 -21
  7. package/dist/index.js +968 -376
  8. package/dist/index.js.map +1 -1
  9. package/dist/nextjs.cjs +74 -6
  10. package/dist/nextjs.cjs.map +1 -1
  11. package/dist/nextjs.d.cts +149 -0
  12. package/dist/nextjs.d.ts +149 -0
  13. package/dist/nextjs.js +74 -6
  14. package/dist/nextjs.js.map +1 -1
  15. package/dist/server.cjs +665 -27
  16. package/dist/server.cjs.map +1 -1
  17. package/dist/server.d.cts +357 -3
  18. package/dist/server.d.ts +357 -3
  19. package/dist/server.js +643 -29
  20. package/dist/server.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/ArticleCard.tsx +42 -6
  23. package/src/ArticleContent.tsx +144 -5
  24. package/src/ArticleDetailHero.tsx +33 -1
  25. package/src/ArticleNavigation.tsx +32 -1
  26. package/src/ArticleSchemas.tsx +43 -39
  27. package/src/ArticleSocialShare.tsx +54 -10
  28. package/src/ArticlesPage.tsx +56 -5
  29. package/src/AuthorArticlesPage.tsx +308 -14
  30. package/src/AuthorCard.tsx +1 -1
  31. package/src/CategoryArticlesPage.tsx +34 -2
  32. package/src/LatestArticles.tsx +28 -1
  33. package/src/LatestArticlesSection.tsx +15 -1
  34. package/src/PaginationNav.tsx +78 -0
  35. package/src/RelatedArticlesSection.tsx +58 -0
  36. package/src/SeriesArticlesPage.tsx +66 -0
  37. package/src/__tests__/ArticleCard.test.tsx +63 -3
  38. package/src/__tests__/ArticleContent.test.tsx +143 -0
  39. package/src/__tests__/ArticleDetailHero.test.tsx +30 -0
  40. package/src/__tests__/ArticleNavigation.test.tsx +81 -3
  41. package/src/__tests__/ArticleSchemas.test.tsx +155 -81
  42. package/src/__tests__/ArticleSocialShare.test.tsx +54 -0
  43. package/src/__tests__/ArticlesPage.test.tsx +131 -0
  44. package/src/__tests__/AuthorArticlesPage.test.tsx +304 -3
  45. package/src/__tests__/CategoryArticlesPage.test.tsx +116 -1
  46. package/src/__tests__/LatestArticles.test.tsx +52 -0
  47. package/src/__tests__/LatestArticlesSection.test.tsx +28 -0
  48. package/src/__tests__/PaginationNav.test.tsx +73 -0
  49. package/src/__tests__/RelatedArticlesSection.test.tsx +132 -0
  50. package/src/__tests__/SeriesArticlesPage.test.tsx +121 -0
  51. package/src/__tests__/eventTracking.test.tsx +145 -0
  52. package/src/__tests__/events.test.ts +82 -0
  53. package/src/__tests__/markdown.test.ts +78 -1
  54. package/src/__tests__/pagination.test.ts +178 -0
  55. package/src/__tests__/seoUtils-authors.test.ts +37 -0
  56. package/src/__tests__/seoUtils.test.ts +246 -0
  57. package/src/__tests__/server-articles.test.ts +356 -1
  58. package/src/__tests__/validateArticles.test.ts +312 -0
  59. package/src/articleTypes.ts +109 -0
  60. package/src/articlesConfig.ts +45 -1
  61. package/src/eventTracking.tsx +97 -0
  62. package/src/events.ts +105 -0
  63. package/src/index.ts +26 -1
  64. package/src/markdown.ts +41 -0
  65. package/src/pagination.ts +93 -0
  66. package/src/seoUtils.ts +198 -11
  67. package/src/server-articles.ts +199 -6
  68. package/src/server.ts +46 -2
  69. package/src/validateArticles.ts +260 -0
package/dist/index.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,103 @@ 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
+ var _a;
593
+ 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: [
594
+ /* @__PURE__ */ jsx8("div", { className: "p-0", children: /* @__PURE__ */ jsx8(
595
+ Image4,
520
596
  {
521
597
  src: article.featuredImage,
522
598
  alt: article.title,
@@ -525,35 +601,67 @@ function ArticleCard({ article }) {
525
601
  height: 200
526
602
  }
527
603
  ) }),
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,
604
+ /* @__PURE__ */ jsxs8("div", { className: "p-6", children: [
605
+ /* @__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 }) }),
606
+ /* @__PURE__ */ jsx8(
607
+ "h3",
532
608
  {
533
- href: `/articles/${article.slug}`,
534
- className: "hover:text-indigo-600 transition-colors",
535
- children: article.title
609
+ className: "text-lg font-semibold leading-none tracking-tight mb-2",
610
+ style: { fontFamily: (_a = config == null ? void 0 : config.theme) == null ? void 0 : _a.headerFontFamily },
611
+ children: /* @__PURE__ */ jsx8(Link6, { href: `/articles/${article.slug}`, className: "hover:text-primary transition-colors", children: article.title })
536
612
  }
537
- ) }),
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
+ ),
614
+ /* @__PURE__ */ jsx8("p", { className: "text-muted-foreground mb-4 line-clamp-3", children: article.excerpt }),
615
+ article.authorSlug && /* @__PURE__ */ jsxs8(
616
+ Link6,
617
+ {
618
+ href: `/articles/authors/${article.authorSlug}`,
619
+ className: "mb-4 flex items-center gap-2 text-sm text-muted-foreground hover:text-primary transition-colors",
620
+ onClick: () => emitArticleEvent(config == null ? void 0 : config.onEvent, {
621
+ name: "author_clicked",
622
+ articleSlug: article.slug,
623
+ authorSlug: article.authorSlug
624
+ }),
625
+ children: [
626
+ article.authorAvatar ? /* @__PURE__ */ jsx8(
627
+ Image4,
628
+ {
629
+ src: article.authorAvatar,
630
+ alt: "",
631
+ width: 24,
632
+ height: 24,
633
+ className: "h-6 w-6 rounded-full object-cover"
634
+ }
635
+ ) : /* @__PURE__ */ jsx8(
636
+ "span",
637
+ {
638
+ "aria-hidden": "true",
639
+ className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground",
640
+ children: getInitials(article.author)
641
+ }
642
+ ),
643
+ /* @__PURE__ */ jsx8("span", { children: article.author })
644
+ ]
645
+ }
646
+ ),
647
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between text-sm text-muted-foreground border-t pt-4", children: [
648
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-3", children: [
649
+ article.date && /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
650
+ /* @__PURE__ */ jsx8(Calendar2, { className: "h-3 w-3" }),
651
+ /* @__PURE__ */ jsx8("span", { children: article.date })
544
652
  ] }),
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 })
653
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
654
+ /* @__PURE__ */ jsx8(Clock2, { className: "h-3 w-3" }),
655
+ /* @__PURE__ */ jsx8("span", { children: article.readTime })
548
656
  ] })
549
657
  ] }),
550
- /* @__PURE__ */ jsx7(
551
- Link5,
658
+ /* @__PURE__ */ jsx8(
659
+ Link6,
552
660
  {
553
661
  href: `/articles/${article.slug}`,
554
662
  className: "inline-flex items-center justify-center h-8 w-8 p-0 rounded-md hover:bg-accent transition-colors",
555
663
  "aria-label": `Read ${article.title}`,
556
- children: /* @__PURE__ */ jsx7(ArrowRight2, { className: "h-4 w-4" })
664
+ children: /* @__PURE__ */ jsx8(ArrowRight2, { className: "h-4 w-4" })
557
665
  }
558
666
  )
559
667
  ] })
@@ -561,18 +669,114 @@ function ArticleCard({ article }) {
561
669
  ] });
562
670
  }
563
671
 
672
+ // src/PaginationNav.tsx
673
+ import Link7 from "next/link";
674
+ import { ChevronLeft, ChevronRight } from "lucide-react";
675
+
676
+ // src/pagination.ts
677
+ function buildPageUrl(basePath, page) {
678
+ const base = basePath.replace(/\/$/, "");
679
+ return page > 1 ? `${base}/page/${page}` : base;
680
+ }
681
+ function buildPaginationLinks(basePath, page, totalPages) {
682
+ return {
683
+ canonicalUrl: buildPageUrl(basePath, page),
684
+ prevUrl: page > 1 ? buildPageUrl(basePath, page - 1) : null,
685
+ nextUrl: page < totalPages ? buildPageUrl(basePath, page + 1) : null
686
+ };
687
+ }
688
+
689
+ // src/PaginationNav.tsx
690
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
691
+ function PaginationNav({ basePath, page, totalPages }) {
692
+ if (totalPages <= 1) return null;
693
+ const { prevUrl, nextUrl } = buildPaginationLinks(basePath, page, totalPages);
694
+ return /* @__PURE__ */ jsxs9(
695
+ "nav",
696
+ {
697
+ className: "mt-10 flex items-center justify-center gap-4",
698
+ "aria-label": "Article listing pagination",
699
+ children: [
700
+ prevUrl && /* @__PURE__ */ jsx9("link", { rel: "prev", href: prevUrl }),
701
+ nextUrl && /* @__PURE__ */ jsx9("link", { rel: "next", href: nextUrl }),
702
+ prevUrl ? /* @__PURE__ */ jsxs9(
703
+ Link7,
704
+ {
705
+ href: prevUrl,
706
+ 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",
707
+ children: [
708
+ /* @__PURE__ */ jsx9(ChevronLeft, { className: "h-4 w-4", "aria-hidden": "true" }),
709
+ "Previous"
710
+ ]
711
+ }
712
+ ) : /* @__PURE__ */ jsxs9(
713
+ "span",
714
+ {
715
+ "aria-hidden": "true",
716
+ className: "inline-flex items-center gap-1 px-4 py-2 rounded-md font-medium text-sm text-muted-foreground opacity-50",
717
+ children: [
718
+ /* @__PURE__ */ jsx9(ChevronLeft, { className: "h-4 w-4" }),
719
+ "Previous"
720
+ ]
721
+ }
722
+ ),
723
+ /* @__PURE__ */ jsxs9("span", { className: "text-sm text-muted-foreground", children: [
724
+ "Page ",
725
+ page,
726
+ " of ",
727
+ totalPages
728
+ ] }),
729
+ nextUrl ? /* @__PURE__ */ jsxs9(
730
+ Link7,
731
+ {
732
+ href: nextUrl,
733
+ 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",
734
+ children: [
735
+ "Next",
736
+ /* @__PURE__ */ jsx9(ChevronRight, { className: "h-4 w-4", "aria-hidden": "true" })
737
+ ]
738
+ }
739
+ ) : /* @__PURE__ */ jsxs9(
740
+ "span",
741
+ {
742
+ "aria-hidden": "true",
743
+ className: "inline-flex items-center gap-1 px-4 py-2 rounded-md font-medium text-sm text-muted-foreground opacity-50",
744
+ children: [
745
+ "Next",
746
+ /* @__PURE__ */ jsx9(ChevronRight, { className: "h-4 w-4" })
747
+ ]
748
+ }
749
+ )
750
+ ]
751
+ }
752
+ );
753
+ }
754
+
564
755
  // src/LatestArticles.tsx
565
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
566
- function LatestArticles({ articles, pageSize }) {
756
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
757
+ function LatestArticles({ articles, pageSize, pagination }) {
567
758
  const [visibleCount, setVisibleCount] = useState2(pageSize);
568
759
  useEffect2(() => {
569
760
  setVisibleCount(pageSize);
570
761
  }, [articles, pageSize]);
762
+ if (pagination) {
763
+ return /* @__PURE__ */ jsxs10("div", { children: [
764
+ /* @__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)) }),
765
+ /* @__PURE__ */ jsx10(
766
+ PaginationNav,
767
+ {
768
+ basePath: pagination.basePath,
769
+ page: pagination.page,
770
+ totalPages: pagination.totalPages
771
+ }
772
+ )
773
+ ] });
774
+ }
571
775
  const visible = articles.slice(0, visibleCount);
572
776
  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(
777
+ return /* @__PURE__ */ jsxs10("div", { children: [
778
+ /* @__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)) }),
779
+ hasMore && /* @__PURE__ */ jsx10("div", { className: "mt-10 text-center", children: /* @__PURE__ */ jsx10(
576
780
  "button",
577
781
  {
578
782
  type: "button",
@@ -585,35 +789,36 @@ function LatestArticles({ articles, pageSize }) {
585
789
  }
586
790
 
587
791
  // src/LatestArticlesSection.tsx
588
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
792
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
589
793
  function DescriptionText({
590
794
  searchQuery,
591
795
  count
592
796
  }) {
593
797
  const pluralSuffix = count === 1 ? "" : "s";
594
798
  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 });
799
+ return /* @__PURE__ */ jsx11("p", { className: "text-lg text-muted-foreground", children: text });
596
800
  }
597
801
  function LatestArticlesSection({
598
802
  articles,
599
803
  searchQuery,
600
804
  onClearSearch,
601
- pageSize = 6
805
+ pageSize = 6,
806
+ pagination
602
807
  }) {
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 })
808
+ 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: [
809
+ /* @__PURE__ */ jsxs11("div", { className: "text-center mb-12", children: [
810
+ /* @__PURE__ */ jsx11("h2", { className: "text-3xl font-bold text-foreground mb-4", children: searchQuery ? "Search Results" : "Latest Articles" }),
811
+ /* @__PURE__ */ jsx11(DescriptionText, { searchQuery, count: articles.length })
607
812
  ] }),
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: [
813
+ articles.length === 0 && searchQuery ? /* @__PURE__ */ jsx11("div", { className: "text-center py-12", children: /* @__PURE__ */ jsxs11("div", { className: "max-w-md mx-auto", children: [
814
+ /* @__PURE__ */ jsx11(Search2, { className: "h-12 w-12 text-muted-foreground mx-auto mb-4" }),
815
+ /* @__PURE__ */ jsx11("h3", { className: "text-lg font-semibold text-foreground mb-2", children: "No articles found" }),
816
+ /* @__PURE__ */ jsxs11("p", { className: "text-muted-foreground mb-4", children: [
612
817
  `We couldn't find any articles matching "`,
613
818
  searchQuery,
614
819
  '". Try adjusting your search terms.'
615
820
  ] }),
616
- /* @__PURE__ */ jsx9(
821
+ /* @__PURE__ */ jsx11(
617
822
  "button",
618
823
  {
619
824
  type: "button",
@@ -622,7 +827,15 @@ function LatestArticlesSection({
622
827
  children: "Clear search"
623
828
  }
624
829
  )
625
- ] }) }) : /* @__PURE__ */ jsx9(LatestArticles, { articles, pageSize }, searchQuery)
830
+ ] }) }) : /* @__PURE__ */ jsx11(
831
+ LatestArticles,
832
+ {
833
+ articles,
834
+ pageSize,
835
+ pagination: searchQuery ? void 0 : pagination
836
+ },
837
+ searchQuery
838
+ )
626
839
  ] }) });
627
840
  }
628
841
 
@@ -715,11 +928,12 @@ function useArticles(initialArticles, initialCategories) {
715
928
  }
716
929
 
717
930
  // src/ArticlesPage.tsx
718
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
931
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
719
932
  function buildThemeVars(theme) {
720
933
  if (!theme) return {};
721
934
  const vars = {};
722
935
  if (theme.fontFamily) vars["--articles-font-family"] = theme.fontFamily;
936
+ if (theme.headerFontFamily) vars["--articles-header-font-family"] = theme.headerFontFamily;
723
937
  if (theme.headerColor) vars["--articles-header-color"] = theme.headerColor;
724
938
  if (theme.textColor) vars["--articles-text-color"] = theme.textColor;
725
939
  if (theme.backgroundColor) vars["--articles-bg-color"] = theme.backgroundColor;
@@ -744,11 +958,12 @@ function renderSection(section, ctx, config) {
744
958
  error,
745
959
  handleSearch,
746
960
  pageSize,
747
- categoriesPageSize
961
+ categoriesPageSize,
962
+ pagination
748
963
  } = ctx;
749
964
  switch (section) {
750
965
  case "hero":
751
- return /* @__PURE__ */ jsx10(
966
+ return /* @__PURE__ */ jsx12(
752
967
  ArticlesHero,
753
968
  {
754
969
  title: (_a = config.hero) == null ? void 0 : _a.title,
@@ -757,7 +972,7 @@ function renderSection(section, ctx, config) {
757
972
  "hero"
758
973
  );
759
974
  case "search":
760
- return /* @__PURE__ */ jsx10(
975
+ return /* @__PURE__ */ jsx12(
761
976
  ArticleSearchBar,
762
977
  {
763
978
  value: searchQuery,
@@ -768,7 +983,7 @@ function renderSection(section, ctx, config) {
768
983
  "search"
769
984
  );
770
985
  case "featured":
771
- return articles.length > 0 && !searchQuery ? /* @__PURE__ */ jsx10(
986
+ return articles.length > 0 && !searchQuery ? /* @__PURE__ */ jsx12(
772
987
  FeaturedArticle,
773
988
  {
774
989
  article: articles[0],
@@ -778,16 +993,16 @@ function renderSection(section, ctx, config) {
778
993
  ) : null;
779
994
  case "latest":
780
995
  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..." })
996
+ 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: [
997
+ /* @__PURE__ */ jsx12("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4" }),
998
+ /* @__PURE__ */ jsx12("p", { className: "text-muted-foreground", children: "Loading articles..." })
784
999
  ] }) }) }, "latest");
785
1000
  }
786
1001
  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(
1002
+ 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: [
1003
+ /* @__PURE__ */ jsx12("h3", { className: "text-lg font-semibold text-red-800 mb-2", children: "Error Loading Articles" }),
1004
+ /* @__PURE__ */ jsx12("p", { className: "text-red-600 mb-4", children: error }),
1005
+ /* @__PURE__ */ jsx12(
791
1006
  "button",
792
1007
  {
793
1008
  type: "button",
@@ -798,18 +1013,19 @@ function renderSection(section, ctx, config) {
798
1013
  )
799
1014
  ] }) }) }, "latest");
800
1015
  }
801
- return /* @__PURE__ */ jsx10(
1016
+ return /* @__PURE__ */ jsx12(
802
1017
  LatestArticlesSection,
803
1018
  {
804
1019
  articles: displayedArticles,
805
1020
  searchQuery,
806
1021
  onClearSearch: () => handleSearch(""),
807
- pageSize
1022
+ pageSize,
1023
+ pagination
808
1024
  },
809
1025
  "latest"
810
1026
  );
811
1027
  case "categories":
812
- return searchQuery ? null : /* @__PURE__ */ jsx10(
1028
+ return searchQuery ? null : /* @__PURE__ */ jsx12(
813
1029
  ArticleCategoryGrid,
814
1030
  {
815
1031
  categories,
@@ -825,7 +1041,10 @@ function renderSection(section, ctx, config) {
825
1041
  function ArticlesPage({
826
1042
  config,
827
1043
  initialArticles,
828
- initialCategories
1044
+ initialCategories,
1045
+ page,
1046
+ totalPages,
1047
+ totalCount
829
1048
  }) {
830
1049
  var _a, _b, _c;
831
1050
  const state = useArticles(initialArticles, initialCategories);
@@ -833,19 +1052,34 @@ function ArticlesPage({
833
1052
  const pageSize = (_b = config.pageSize) != null ? _b : DEFAULT_PAGE_SIZE;
834
1053
  const categoriesPageSize = (_c = config.categoriesPageSize) != null ? _c : DEFAULT_CATEGORIES_PAGE_SIZE;
835
1054
  const themeVars = buildThemeVars(config.theme);
836
- const hasFeatured = layout.includes("featured");
1055
+ const isPagesMode = config.listingPagination === "pages" && page !== void 0 && totalPages !== void 0;
1056
+ const currentPage = page != null ? page : 1;
1057
+ const effectiveLayout = isPagesMode && currentPage > 1 ? layout.filter((section) => section !== "featured") : layout;
1058
+ const hasFeatured = effectiveLayout.includes("featured");
837
1059
  const articlesWithoutFeatured = hasFeatured ? state.articles.slice(1) : state.articles;
838
1060
  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(
1061
+ const pagination = isPagesMode ? { page: currentPage, totalPages, basePath: "/articles" } : void 0;
1062
+ const ctx = __spreadProps(__spreadValues({}, state), {
1063
+ displayedArticles,
1064
+ pageSize,
1065
+ categoriesPageSize,
1066
+ pagination
1067
+ });
1068
+ const articleCount = totalCount != null ? totalCount : state.articles.length;
1069
+ return /* @__PURE__ */ jsxs12("div", { style: themeVars, children: [
1070
+ effectiveLayout.map((section) => renderSection(section, ctx, config)),
1071
+ state.articles.length > 0 && /* @__PURE__ */ jsx12(
843
1072
  CollectionPageSchema,
844
1073
  {
845
1074
  title: `Articles | ${config.siteName}`,
846
- description: `Browse all ${state.articles.length} articles on ${config.siteName}.`,
1075
+ description: `Browse all ${articleCount} articles on ${config.siteName}.`,
847
1076
  url: `${config.siteUrl}/articles`,
848
- articleCount: state.articles.length
1077
+ articleCount,
1078
+ items: displayedArticles.slice(0, pageSize).map((article, index) => ({
1079
+ position: index + 1,
1080
+ url: `${config.siteUrl}/articles/${article.slug}`,
1081
+ name: article.title
1082
+ }))
849
1083
  }
850
1084
  )
851
1085
  ] });
@@ -853,10 +1087,10 @@ function ArticlesPage({
853
1087
 
854
1088
  // src/ArticleDetailHero.tsx
855
1089
  import { Fragment as Fragment3 } from "react";
856
- import Image4 from "next/image";
857
- import Link6 from "next/link";
1090
+ import Image5 from "next/image";
1091
+ import Link8 from "next/link";
858
1092
  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";
1093
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
860
1094
  function toSlug(cat) {
861
1095
  return cat.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
862
1096
  }
@@ -865,11 +1099,13 @@ function ArticleDetailHero({
865
1099
  categoryBasePath = "/articles/category",
866
1100
  showDate = false,
867
1101
  showAuthor = true,
868
- authors = []
1102
+ authors = [],
1103
+ config
869
1104
  }) {
1105
+ var _a;
870
1106
  const showConfiguredAuthors = showAuthor && authors.length > 0;
871
1107
  const showLegacyAuthor = showAuthor && authors.length === 0 && article.author.trim().length > 0;
872
- return /* @__PURE__ */ jsxs11(
1108
+ return /* @__PURE__ */ jsxs13(
873
1109
  "section",
874
1110
  {
875
1111
  style: {
@@ -881,8 +1117,8 @@ function ArticleDetailHero({
881
1117
  overflow: "hidden"
882
1118
  },
883
1119
  children: [
884
- /* @__PURE__ */ jsx11(
885
- Image4,
1120
+ /* @__PURE__ */ jsx13(
1121
+ Image5,
886
1122
  {
887
1123
  src: article.featuredImage,
888
1124
  alt: article.title,
@@ -893,7 +1129,7 @@ function ArticleDetailHero({
893
1129
  priority: true
894
1130
  }
895
1131
  ),
896
- /* @__PURE__ */ jsx11(
1132
+ /* @__PURE__ */ jsx13(
897
1133
  "div",
898
1134
  {
899
1135
  style: {
@@ -903,7 +1139,7 @@ function ArticleDetailHero({
903
1139
  }
904
1140
  }
905
1141
  ),
906
- /* @__PURE__ */ jsxs11(
1142
+ /* @__PURE__ */ jsxs13(
907
1143
  "div",
908
1144
  {
909
1145
  style: {
@@ -917,7 +1153,7 @@ function ArticleDetailHero({
917
1153
  width: "100%"
918
1154
  },
919
1155
  children: [
920
- article.categories && article.categories.length > 0 && /* @__PURE__ */ jsx11(
1156
+ article.categories && article.categories.length > 0 && /* @__PURE__ */ jsx13(
921
1157
  "div",
922
1158
  {
923
1159
  style: {
@@ -928,15 +1164,15 @@ function ArticleDetailHero({
928
1164
  marginBottom: "1rem"
929
1165
  },
930
1166
  children: article.categories.slice(0, 4).map(
931
- (cat) => categoryBasePath ? /* @__PURE__ */ jsx11(
932
- Link6,
1167
+ (cat) => categoryBasePath ? /* @__PURE__ */ jsx13(
1168
+ Link8,
933
1169
  {
934
1170
  href: `${categoryBasePath}/${toSlug(cat)}`,
935
1171
  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
1172
  children: cat
937
1173
  },
938
1174
  cat
939
- ) : /* @__PURE__ */ jsx11(
1175
+ ) : /* @__PURE__ */ jsx13(
940
1176
  "span",
941
1177
  {
942
1178
  className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full",
@@ -947,8 +1183,15 @@ function ArticleDetailHero({
947
1183
  )
948
1184
  }
949
1185
  ),
950
- /* @__PURE__ */ jsx11("h1", { className: "text-4xl md:text-5xl font-bold mb-4", children: article.title }),
951
- /* @__PURE__ */ jsxs11(
1186
+ /* @__PURE__ */ jsx13(
1187
+ "h1",
1188
+ {
1189
+ className: "text-4xl md:text-5xl font-bold mb-4",
1190
+ style: { fontFamily: (_a = config == null ? void 0 : config.theme) == null ? void 0 : _a.headerFontFamily },
1191
+ children: article.title
1192
+ }
1193
+ ),
1194
+ /* @__PURE__ */ jsxs13(
952
1195
  "div",
953
1196
  {
954
1197
  style: {
@@ -960,23 +1203,23 @@ function ArticleDetailHero({
960
1203
  color: "rgba(255,255,255,0.8)"
961
1204
  },
962
1205
  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 })
1206
+ showDate && article.date && /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1207
+ /* @__PURE__ */ jsx13(Calendar3, { className: "h-4 w-4" }),
1208
+ /* @__PURE__ */ jsx13("span", { children: article.date })
966
1209
  ] }),
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 })
1210
+ /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1211
+ /* @__PURE__ */ jsx13(Clock3, { className: "h-4 w-4" }),
1212
+ /* @__PURE__ */ jsx13("span", { children: article.readTime })
970
1213
  ] }),
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: [
1214
+ showConfiguredAuthors && /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1215
+ /* @__PURE__ */ jsx13(User2, { className: "h-4 w-4" }),
1216
+ /* @__PURE__ */ jsxs13("span", { children: [
974
1217
  "By",
975
1218
  " ",
976
- authors.map((author, index) => /* @__PURE__ */ jsxs11(Fragment3, { children: [
1219
+ authors.map((author, index) => /* @__PURE__ */ jsxs13(Fragment3, { children: [
977
1220
  index > 0 && ", ",
978
- /* @__PURE__ */ jsx11(
979
- Link6,
1221
+ /* @__PURE__ */ jsx13(
1222
+ Link8,
980
1223
  {
981
1224
  href: `/articles/authors/${author.slug}`,
982
1225
  className: "font-medium text-white underline-offset-4 hover:underline",
@@ -986,15 +1229,26 @@ function ArticleDetailHero({
986
1229
  ] }, author.slug))
987
1230
  ] })
988
1231
  ] }),
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: [
1232
+ showLegacyAuthor && /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1233
+ /* @__PURE__ */ jsx13(User2, { className: "h-4 w-4" }),
1234
+ /* @__PURE__ */ jsxs13("span", { children: [
992
1235
  "By ",
993
1236
  article.author
994
1237
  ] })
995
1238
  ] })
996
1239
  ]
997
1240
  }
1241
+ ),
1242
+ showConfiguredAuthors && authors.length === 1 && authors[0].promise && /* @__PURE__ */ jsx13(
1243
+ "p",
1244
+ {
1245
+ style: {
1246
+ marginTop: "0.75rem",
1247
+ fontSize: "0.9375rem",
1248
+ color: "rgba(255,255,255,0.85)"
1249
+ },
1250
+ children: authors[0].promise
1251
+ }
998
1252
  )
999
1253
  ]
1000
1254
  }
@@ -1005,9 +1259,9 @@ function ArticleDetailHero({
1005
1259
  }
1006
1260
 
1007
1261
  // 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";
1262
+ import Image6 from "next/image";
1263
+ import Link9 from "next/link";
1264
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1011
1265
  function getCategoryDescription(config, slug, categoryName) {
1012
1266
  var _a;
1013
1267
  const raw = (_a = config.categoryDescriptions) == null ? void 0 : _a[slug];
@@ -1049,7 +1303,14 @@ function buildCategoryBreadcrumbEntry(entry, context) {
1049
1303
  }
1050
1304
  return [{ name: context.categoryName }];
1051
1305
  }
1052
- function CategoryArticlesPage({ category, articles, config }) {
1306
+ function CategoryArticlesPage({
1307
+ category,
1308
+ articles,
1309
+ config,
1310
+ page,
1311
+ totalPages,
1312
+ totalCount
1313
+ }) {
1053
1314
  var _a;
1054
1315
  if (articles.length === 0) return null;
1055
1316
  const categoryName = articles[0].category;
@@ -1058,8 +1319,25 @@ function CategoryArticlesPage({ category, articles, config }) {
1058
1319
  const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1059
1320
  const breadcrumbConfig = getBreadcrumbsConfig(config);
1060
1321
  const breadcrumbItems = buildCategoryBreadcrumbItems(config, categoryName);
1061
- return /* @__PURE__ */ jsxs12("div", { children: [
1062
- breadcrumbsAreEnabled(config) && /* @__PURE__ */ jsx12(
1322
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1323
+ const articleCount = totalCount != null ? totalCount : articles.length;
1324
+ const pagination = config.listingPagination === "pages" && page !== void 0 && totalPages !== void 0 ? { page, totalPages, basePath: `/articles/category/${category}` } : void 0;
1325
+ return /* @__PURE__ */ jsxs14("div", { children: [
1326
+ /* @__PURE__ */ jsx14(
1327
+ CollectionPageSchema,
1328
+ {
1329
+ title: `${categoryName} | ${config.siteName}`,
1330
+ description: description.short,
1331
+ url: `${siteUrl}/articles/category/${category}`,
1332
+ articleCount,
1333
+ items: articles.slice(0, pageSize).map((article, index) => ({
1334
+ position: index + 1,
1335
+ url: `${siteUrl}/articles/${article.slug}`,
1336
+ name: article.title
1337
+ }))
1338
+ }
1339
+ ),
1340
+ breadcrumbsAreEnabled(config) && /* @__PURE__ */ jsx14(
1063
1341
  Breadcrumb,
1064
1342
  {
1065
1343
  items: breadcrumbItems,
@@ -1067,7 +1345,7 @@ function CategoryArticlesPage({ category, articles, config }) {
1067
1345
  showSchema: breadcrumbConfig.showSchema !== false
1068
1346
  }
1069
1347
  ),
1070
- /* @__PURE__ */ jsxs12(
1348
+ /* @__PURE__ */ jsxs14(
1071
1349
  "section",
1072
1350
  {
1073
1351
  style: {
@@ -1079,8 +1357,8 @@ function CategoryArticlesPage({ category, articles, config }) {
1079
1357
  overflow: "hidden"
1080
1358
  },
1081
1359
  children: [
1082
- /* @__PURE__ */ jsx12(
1083
- Image5,
1360
+ /* @__PURE__ */ jsx14(
1361
+ Image6,
1084
1362
  {
1085
1363
  src: heroImage,
1086
1364
  alt: categoryName,
@@ -1090,8 +1368,8 @@ function CategoryArticlesPage({ category, articles, config }) {
1090
1368
  priority: true
1091
1369
  }
1092
1370
  ),
1093
- /* @__PURE__ */ jsx12("div", { style: { position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.6)" } }),
1094
- /* @__PURE__ */ jsxs12(
1371
+ /* @__PURE__ */ jsx14("div", { style: { position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.6)" } }),
1372
+ /* @__PURE__ */ jsxs14(
1095
1373
  "div",
1096
1374
  {
1097
1375
  style: {
@@ -1102,10 +1380,10 @@ function CategoryArticlesPage({ category, articles, config }) {
1102
1380
  padding: "0 1rem"
1103
1381
  },
1104
1382
  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: [
1383
+ /* @__PURE__ */ jsx14("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
1384
+ /* @__PURE__ */ jsx14("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
1385
+ description.long && /* @__PURE__ */ jsx14("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
1386
+ /* @__PURE__ */ jsxs14("p", { className: "text-lg opacity-70 mt-2", children: [
1109
1387
  articles.length,
1110
1388
  " article",
1111
1389
  articles.length === 1 ? "" : "s"
@@ -1116,150 +1394,75 @@ function CategoryArticlesPage({ category, articles, config }) {
1116
1394
  ]
1117
1395
  }
1118
1396
  ),
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 })
1397
+ /* @__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: [
1398
+ /* @__PURE__ */ jsxs14("div", { className: "mb-8 flex items-center justify-between", children: [
1399
+ /* @__PURE__ */ jsx14(Link9, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
1400
+ /* @__PURE__ */ jsx14("p", { className: "text-sm text-muted-foreground", children: description.short })
1123
1401
  ] }),
1124
- /* @__PURE__ */ jsx12(LatestArticles, { articles, pageSize })
1402
+ /* @__PURE__ */ jsx14(LatestArticles, { articles, pageSize, pagination })
1125
1403
  ] }) })
1126
1404
  ] });
1127
1405
  }
1128
1406
 
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 }) {
1407
+ // src/SeriesArticlesPage.tsx
1408
+ import Link10 from "next/link";
1409
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1410
+ function SeriesArticlesPage({ seriesSlug, articles, config }) {
1151
1411
  var _a;
1152
- const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1153
- return /* @__PURE__ */ jsxs13("div", { children: [
1154
- /* @__PURE__ */ jsx13(
1155
- "script",
1412
+ if (articles.length === 0) return null;
1413
+ const seriesName = (_a = articles[0].series) != null ? _a : seriesSlug;
1414
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1415
+ const seriesUrl = `${siteUrl}/articles/series/${seriesSlug}`;
1416
+ return /* @__PURE__ */ jsxs15("div", { children: [
1417
+ /* @__PURE__ */ jsx15(
1418
+ CollectionPageSchema,
1156
1419
  {
1157
- type: "application/ld+json",
1158
- dangerouslySetInnerHTML: {
1159
- __html: JSON.stringify(getPersonSchema(author, config, articles.length))
1160
- }
1420
+ title: `${seriesName} | ${config.siteName}`,
1421
+ description: `Follow the ${seriesName} series on ${config.siteName}.`,
1422
+ url: seriesUrl,
1423
+ articleCount: articles.length,
1424
+ items: articles.map((article, index) => ({
1425
+ position: index + 1,
1426
+ url: `${siteUrl}/articles/${article.slug}`,
1427
+ name: article.title
1428
+ }))
1161
1429
  }
1162
1430
  ),
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
1431
+ /* @__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: [
1432
+ /* @__PURE__ */ jsxs15("div", { className: "mb-8 flex items-center justify-between", children: [
1433
+ /* @__PURE__ */ jsx15(Link10, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
1434
+ /* @__PURE__ */ jsxs15("p", { className: "text-sm text-muted-foreground", children: [
1435
+ articles.length,
1436
+ " article",
1437
+ articles.length === 1 ? "" : "s",
1438
+ " in this series"
1169
1439
  ] })
1170
1440
  ] }),
1171
- /* @__PURE__ */ jsx13(LatestArticles, { articles, pageSize })
1441
+ /* @__PURE__ */ jsx15("h1", { className: "text-3xl font-bold text-foreground mb-8", children: seriesName }),
1442
+ /* @__PURE__ */ jsx15(
1443
+ LatestArticles,
1444
+ {
1445
+ articles,
1446
+ pageSize: Math.max(articles.length, DEFAULT_PAGE_SIZE)
1447
+ }
1448
+ )
1172
1449
  ] }) })
1173
1450
  ] });
1174
1451
  }
1175
1452
 
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
- }
1453
+ // src/AuthorArticlesPage.tsx
1454
+ import Link11 from "next/link";
1252
1455
 
1253
1456
  // src/AuthorDetailHero.tsx
1254
1457
  import Image7 from "next/image";
1255
- import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1458
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1256
1459
  function getInitials2(name) {
1257
1460
  return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
1258
1461
  }
1259
1462
  function AuthorDetailHero({ author, articleCount }) {
1260
1463
  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(
1464
+ 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: [
1465
+ avatar ? /* @__PURE__ */ jsx16(
1263
1466
  Image7,
1264
1467
  {
1265
1468
  src: avatar,
@@ -1269,27 +1472,303 @@ function AuthorDetailHero({ author, articleCount }) {
1269
1472
  className: "h-28 w-28 rounded-full object-cover",
1270
1473
  priority: true
1271
1474
  }
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: [
1475
+ ) : /* @__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) }),
1476
+ /* @__PURE__ */ jsxs16("div", { children: [
1477
+ /* @__PURE__ */ jsx16("p", { className: "mb-3 text-sm font-semibold uppercase tracking-widest text-muted-foreground", children: "Author" }),
1478
+ /* @__PURE__ */ jsx16("h1", { className: "text-4xl font-bold text-foreground md:text-5xl", children: author.name }),
1479
+ /* @__PURE__ */ jsx16("p", { className: "mx-auto mt-4 max-w-2xl text-lg text-muted-foreground", children: author.bio }),
1480
+ typeof articleCount === "number" && /* @__PURE__ */ jsxs16("p", { className: "mt-3 text-sm text-muted-foreground", children: [
1278
1481
  articleCount,
1279
1482
  " article",
1280
1483
  articleCount === 1 ? "" : "s"
1281
1484
  ] })
1282
1485
  ] }),
1283
- /* @__PURE__ */ jsx15(AuthorSocialLinks, { author })
1486
+ /* @__PURE__ */ jsx16(AuthorSocialLinks, { author })
1284
1487
  ] }) });
1285
1488
  }
1286
1489
 
1490
+ // src/eventTracking.tsx
1491
+ import { useEffect as useEffect4, useRef as useRef2 } from "react";
1492
+ import { jsx as jsx17 } from "react/jsx-runtime";
1493
+ var AVERAGE_WORDS_PER_MINUTE = 200;
1494
+ var DEFAULT_MEANINGFUL_READ_MS = 15e3;
1495
+ function ArticleViewTracker({ article, config }) {
1496
+ const firedMeaningfulRead = useRef2(false);
1497
+ useEffect4(() => {
1498
+ emitArticleEvent(config == null ? void 0 : config.onEvent, {
1499
+ name: "article_viewed",
1500
+ articleSlug: article.slug,
1501
+ category: article.category,
1502
+ seriesSlug: article.seriesSlug
1503
+ });
1504
+ const estimatedMs = article.wordCount ? article.wordCount / AVERAGE_WORDS_PER_MINUTE * 6e4 * 0.5 : DEFAULT_MEANINGFUL_READ_MS;
1505
+ const timer = setTimeout(() => {
1506
+ if (firedMeaningfulRead.current) return;
1507
+ firedMeaningfulRead.current = true;
1508
+ emitArticleEvent(config == null ? void 0 : config.onEvent, { name: "meaningful_read", articleSlug: article.slug });
1509
+ }, estimatedMs);
1510
+ return () => clearTimeout(timer);
1511
+ }, [article.slug]);
1512
+ return null;
1513
+ }
1514
+ function CtaViewTracker({ ctaId, articleSlug, config, children }) {
1515
+ const ref = useRef2(null);
1516
+ const fired = useRef2(false);
1517
+ useEffect4(() => {
1518
+ const node = ref.current;
1519
+ if (!node) return;
1520
+ function fire() {
1521
+ if (fired.current) return;
1522
+ fired.current = true;
1523
+ emitArticleEvent(config == null ? void 0 : config.onEvent, { name: "cta_viewed", ctaId, articleSlug });
1524
+ }
1525
+ if (typeof IntersectionObserver === "undefined") {
1526
+ fire();
1527
+ return;
1528
+ }
1529
+ const observer = new IntersectionObserver(
1530
+ (entries) => {
1531
+ if (entries.some((entry) => entry.isIntersecting)) fire();
1532
+ },
1533
+ { threshold: 0.5 }
1534
+ );
1535
+ observer.observe(node);
1536
+ return () => observer.disconnect();
1537
+ }, [ctaId, articleSlug]);
1538
+ return /* @__PURE__ */ jsx17("div", { ref, children });
1539
+ }
1540
+
1541
+ // src/AuthorArticlesPage.tsx
1542
+ import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
1543
+ function getPersonSchema(author, config, articleCount) {
1544
+ var _a, _b;
1545
+ return __spreadProps(__spreadValues(__spreadValues({
1546
+ "@context": "https://schema.org",
1547
+ "@type": "Person",
1548
+ name: author.name,
1549
+ description: author.bio,
1550
+ url: (_a = author.url) != null ? _a : getAuthorUrl(author)
1551
+ }, getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }), {
1552
+ knowsAbout: config.siteName,
1553
+ mainEntityOfPage: (_b = author.url) != null ? _b : `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`,
1554
+ interactionStatistic: {
1555
+ "@type": "InteractionCounter",
1556
+ interactionType: "https://schema.org/WriteAction",
1557
+ userInteractionCount: articleCount
1558
+ }
1559
+ });
1560
+ }
1561
+ function renderHeroSection(author, articleCount) {
1562
+ return /* @__PURE__ */ jsx18(AuthorDetailHero, { author, articleCount }, "hero");
1563
+ }
1564
+ function renderPromiseSection(author) {
1565
+ 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;
1566
+ }
1567
+ function renderServesWhoSection(author) {
1568
+ 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: [
1569
+ /* @__PURE__ */ jsx18("h2", { className: "mb-4 text-center text-2xl font-bold text-foreground", children: "Who I help" }),
1570
+ /* @__PURE__ */ jsx18("ul", { className: "grid gap-3 sm:grid-cols-2", children: author.servesWho.map((who) => /* @__PURE__ */ jsx18(
1571
+ "li",
1572
+ {
1573
+ className: "rounded-lg border border-border bg-card px-4 py-3 text-sm text-card-foreground",
1574
+ children: who
1575
+ },
1576
+ who
1577
+ )) })
1578
+ ] }) }, "servesWho") : null;
1579
+ }
1580
+ function renderOriginStorySection(author) {
1581
+ 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: [
1582
+ /* @__PURE__ */ jsx18("h2", { className: "mb-6 text-2xl font-bold text-foreground", children: "My story" }),
1583
+ /* @__PURE__ */ jsx18("div", { className: "space-y-6", children: author.originStory.map((block, blockIndex) => {
1584
+ var _a;
1585
+ return /* @__PURE__ */ jsxs17("div", { children: [
1586
+ block.heading && /* @__PURE__ */ jsx18("h3", { className: "mb-2 text-lg font-semibold text-foreground", children: block.heading }),
1587
+ block.paragraphs.map((paragraph, paragraphIndex) => {
1588
+ var _a2;
1589
+ return /* @__PURE__ */ jsx18(
1590
+ "p",
1591
+ {
1592
+ className: "mb-3 text-muted-foreground",
1593
+ children: paragraph
1594
+ },
1595
+ `${(_a2 = block.heading) != null ? _a2 : blockIndex}-${paragraph.slice(0, 40)}-${paragraphIndex}`
1596
+ );
1597
+ })
1598
+ ] }, (_a = block.heading) != null ? _a : blockIndex);
1599
+ }) })
1600
+ ] }) }, "originStory") : null;
1601
+ }
1602
+ function renderPrinciplesSection(author) {
1603
+ 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: [
1604
+ /* @__PURE__ */ jsx18("h2", { className: "mb-4 text-center text-2xl font-bold text-foreground", children: "What I believe" }),
1605
+ /* @__PURE__ */ jsx18("ul", { className: "space-y-3", children: author.principles.map((principle) => /* @__PURE__ */ jsx18(
1606
+ "li",
1607
+ {
1608
+ className: "rounded-lg border border-border bg-card px-4 py-3 text-sm text-card-foreground",
1609
+ children: principle
1610
+ },
1611
+ principle
1612
+ )) })
1613
+ ] }) }, "principles") : null;
1614
+ }
1615
+ function renderProofSection(author) {
1616
+ 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: [
1617
+ /* @__PURE__ */ jsx18("h2", { className: "mb-6 text-center text-2xl font-bold text-foreground", children: "Proof" }),
1618
+ /* @__PURE__ */ jsx18("div", { className: "grid gap-4 sm:grid-cols-2", children: author.proof.map((item) => {
1619
+ var _a, _b, _c;
1620
+ return /* @__PURE__ */ jsxs17(
1621
+ "div",
1622
+ {
1623
+ className: "rounded-lg border border-border bg-card p-4 text-card-foreground",
1624
+ children: [
1625
+ /* @__PURE__ */ jsx18("p", { className: "font-medium", children: item.claim }),
1626
+ (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 })
1627
+ ]
1628
+ },
1629
+ `${item.claim}-${(_b = (_a = item.url) != null ? _a : item.source) != null ? _b : ""}`
1630
+ );
1631
+ }) })
1632
+ ] }) }, "proof") : null;
1633
+ }
1634
+ function renderCtaSection(author, config) {
1635
+ 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(
1636
+ Link11,
1637
+ {
1638
+ href: author.primaryCta.href,
1639
+ onClick: () => emitArticleEvent(config == null ? void 0 : config.onEvent, {
1640
+ name: "cta_clicked",
1641
+ ctaId: `author-cta:${author.slug}`
1642
+ }),
1643
+ 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",
1644
+ children: author.primaryCta.label
1645
+ }
1646
+ ) }) }) }, "cta") : null;
1647
+ }
1648
+ function renderArticlesSection(author, articles, pageSize, pagination) {
1649
+ 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: [
1650
+ /* @__PURE__ */ jsxs17("div", { className: "mb-8 flex items-center justify-between gap-4", children: [
1651
+ /* @__PURE__ */ jsx18(Link11, { href: "/articles", className: "text-sm text-primary hover:underline", children: "All Articles" }),
1652
+ /* @__PURE__ */ jsxs17("p", { className: "text-sm text-muted-foreground", children: [
1653
+ "Articles by ",
1654
+ author.name
1655
+ ] })
1656
+ ] }),
1657
+ /* @__PURE__ */ jsx18(LatestArticles, { articles, pageSize, pagination })
1658
+ ] }) }, "articles");
1659
+ }
1660
+ function renderAuthorSection(section, ctx) {
1661
+ const { author, articleCount, articles, pageSize, pagination, customSection, config } = ctx;
1662
+ switch (section) {
1663
+ case "hero":
1664
+ return renderHeroSection(author, articleCount);
1665
+ case "promise":
1666
+ return renderPromiseSection(author);
1667
+ case "servesWho":
1668
+ return renderServesWhoSection(author);
1669
+ case "originStory":
1670
+ return renderOriginStorySection(author);
1671
+ case "principles":
1672
+ return renderPrinciplesSection(author);
1673
+ case "proof":
1674
+ return renderProofSection(author);
1675
+ case "cta":
1676
+ return renderCtaSection(author, config);
1677
+ case "articles":
1678
+ return renderArticlesSection(author, articles, pageSize, pagination);
1679
+ case "custom":
1680
+ return customSection != null ? customSection : null;
1681
+ default:
1682
+ return null;
1683
+ }
1684
+ }
1685
+ function AuthorArticlesPage({
1686
+ author,
1687
+ articles,
1688
+ config,
1689
+ page,
1690
+ totalPages,
1691
+ totalCount,
1692
+ sections,
1693
+ customSection
1694
+ }) {
1695
+ var _a, _b;
1696
+ const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
1697
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1698
+ const articleCount = totalCount != null ? totalCount : articles.length;
1699
+ const pagination = config.listingPagination === "pages" && page !== void 0 && totalPages !== void 0 ? { page, totalPages, basePath: `/articles/authors/${author.slug}` } : void 0;
1700
+ const schemas = /* @__PURE__ */ jsxs17(Fragment4, { children: [
1701
+ /* @__PURE__ */ jsx18(
1702
+ "script",
1703
+ {
1704
+ type: "application/ld+json",
1705
+ dangerouslySetInnerHTML: {
1706
+ __html: JSON.stringify(getPersonSchema(author, config, articleCount))
1707
+ }
1708
+ }
1709
+ ),
1710
+ /* @__PURE__ */ jsx18(
1711
+ CollectionPageSchema,
1712
+ {
1713
+ title: `${author.name} | ${config.siteName}`,
1714
+ description: `Articles by ${author.name} on ${config.siteName}.`,
1715
+ url: (_b = author.url) != null ? _b : `${siteUrl}/articles/authors/${author.slug}`,
1716
+ articleCount,
1717
+ items: articles.slice(0, pageSize).map((article, index) => ({
1718
+ position: index + 1,
1719
+ url: `${siteUrl}/articles/${article.slug}`,
1720
+ name: article.title
1721
+ }))
1722
+ }
1723
+ )
1724
+ ] });
1725
+ if (!sections) {
1726
+ return /* @__PURE__ */ jsxs17("div", { children: [
1727
+ schemas,
1728
+ /* @__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: [
1729
+ /* @__PURE__ */ jsxs17("div", { className: "mb-8 flex items-center justify-between gap-4", children: [
1730
+ /* @__PURE__ */ jsx18(Link11, { href: "/articles", className: "text-sm text-primary hover:underline", children: "All Articles" }),
1731
+ /* @__PURE__ */ jsxs17("p", { className: "text-sm text-muted-foreground", children: [
1732
+ "Articles by ",
1733
+ author.name
1734
+ ] })
1735
+ ] }),
1736
+ /* @__PURE__ */ jsx18(LatestArticles, { articles, pageSize, pagination })
1737
+ ] }) })
1738
+ ] });
1739
+ }
1740
+ const sectionCtx = {
1741
+ author,
1742
+ articleCount,
1743
+ articles,
1744
+ pageSize,
1745
+ pagination,
1746
+ customSection,
1747
+ config
1748
+ };
1749
+ return /* @__PURE__ */ jsxs17("div", { children: [
1750
+ schemas,
1751
+ sections.map((section) => renderAuthorSection(section, sectionCtx))
1752
+ ] });
1753
+ }
1754
+
1287
1755
  // src/ArticleSocialShare.tsx
1288
1756
  import { useState as useState4 } from "react";
1289
1757
  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 }) {
1758
+ import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
1759
+ function ArticleSocialShare({
1760
+ title,
1761
+ url,
1762
+ excerpt,
1763
+ shareMessage,
1764
+ config,
1765
+ articleSlug
1766
+ }) {
1292
1767
  const [copied, setCopied] = useState4(false);
1768
+ function trackShare(channel) {
1769
+ if (!articleSlug) return;
1770
+ emitArticleEvent(config == null ? void 0 : config.onEvent, { name: "shared", articleSlug, channel });
1771
+ }
1293
1772
  const encodedTitle = encodeURIComponent(title);
1294
1773
  const encodedUrl = encodeURIComponent(url);
1295
1774
  const encodedExcerpt = encodeURIComponent(excerpt || "");
@@ -1306,102 +1785,211 @@ function ArticleSocialShare({ title, url, excerpt, shareMessage }) {
1306
1785
  try {
1307
1786
  yield navigator.clipboard.writeText(url);
1308
1787
  setCopied(true);
1788
+ trackShare("copy-link");
1309
1789
  setTimeout(() => setCopied(false), 2e3);
1310
1790
  } catch (e) {
1311
1791
  }
1312
1792
  });
1313
- const openShareWindow = (shareUrl) => {
1793
+ const openShareWindow = (shareUrl, channel) => {
1794
+ trackShare(channel);
1314
1795
  globalThis.open(shareUrl, "_blank", "width=600,height=400,scrollbars=yes,resizable=yes");
1315
1796
  };
1316
1797
  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" })
1798
+ return /* @__PURE__ */ jsxs18("div", { className: "border-t border-border pt-8 mt-8", children: [
1799
+ /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2 mb-4", children: [
1800
+ /* @__PURE__ */ jsx19(Share2, { className: "h-5 w-5 text-muted-foreground" }),
1801
+ /* @__PURE__ */ jsx19("h3", { className: "text-lg font-semibold text-foreground", children: "Share this article" })
1321
1802
  ] }),
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" }),
1803
+ /* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap gap-2", children: [
1804
+ /* @__PURE__ */ jsxs18(
1805
+ "button",
1806
+ {
1807
+ type: "button",
1808
+ className: btnClass,
1809
+ onClick: () => openShareWindow(shareLinks.linkedin, "linkedin"),
1810
+ children: [
1811
+ /* @__PURE__ */ jsx19(Users, { className: "h-4 w-4" }),
1812
+ "LinkedIn"
1813
+ ]
1814
+ }
1815
+ ),
1816
+ /* @__PURE__ */ jsxs18(
1817
+ "button",
1818
+ {
1819
+ type: "button",
1820
+ className: btnClass,
1821
+ onClick: () => openShareWindow(shareLinks.facebook, "facebook"),
1822
+ children: [
1823
+ /* @__PURE__ */ jsx19("span", { className: "text-xs font-bold", children: "f" }),
1824
+ " Facebook"
1825
+ ]
1826
+ }
1827
+ ),
1828
+ /* @__PURE__ */ jsxs18(
1829
+ "button",
1830
+ {
1831
+ type: "button",
1832
+ className: btnClass,
1833
+ onClick: () => openShareWindow(shareLinks.twitter, "twitter"),
1834
+ children: [
1835
+ /* @__PURE__ */ jsx19(MessageCircle2, { className: "h-4 w-4" }),
1836
+ "Twitter"
1837
+ ]
1838
+ }
1839
+ ),
1840
+ /* @__PURE__ */ jsxs18(
1841
+ "button",
1842
+ {
1843
+ type: "button",
1844
+ className: btnClass,
1845
+ onClick: () => openShareWindow(shareLinks.reddit, "reddit"),
1846
+ children: [
1847
+ /* @__PURE__ */ jsx19(FileText, { className: "h-4 w-4" }),
1848
+ "Reddit"
1849
+ ]
1850
+ }
1851
+ ),
1852
+ /* @__PURE__ */ jsxs18(
1853
+ "button",
1854
+ {
1855
+ type: "button",
1856
+ className: btnClass,
1857
+ onClick: () => openShareWindow(shareLinks.whatsapp, "whatsapp"),
1858
+ children: [
1859
+ /* @__PURE__ */ jsx19(MessageCircle2, { className: "h-4 w-4" }),
1860
+ "WhatsApp"
1861
+ ]
1862
+ }
1863
+ ),
1864
+ /* @__PURE__ */ jsxs18(
1865
+ "button",
1866
+ {
1867
+ type: "button",
1868
+ className: btnClass,
1869
+ onClick: () => openShareWindow(shareLinks.telegram, "telegram"),
1870
+ children: [
1871
+ /* @__PURE__ */ jsx19(MessageCircle2, { className: "h-4 w-4" }),
1872
+ "Telegram"
1873
+ ]
1874
+ }
1875
+ ),
1876
+ /* @__PURE__ */ jsxs18("a", { className: btnClass, href: shareLinks.email, onClick: () => trackShare("email"), children: [
1877
+ /* @__PURE__ */ jsx19(Mail2, { className: "h-4 w-4" }),
1349
1878
  "Email"
1350
1879
  ] }),
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" }),
1880
+ /* @__PURE__ */ jsxs18("button", { type: "button", className: btnClass, onClick: copyToClipboard, children: [
1881
+ copied ? /* @__PURE__ */ jsx19(Check, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx19(Copy, { className: "h-4 w-4" }),
1353
1882
  copied ? "Copied!" : "Copy Link"
1354
1883
  ] })
1355
1884
  ] }),
1356
- shareMessage && /* @__PURE__ */ jsx16("p", { className: "text-sm text-muted-foreground mt-3", children: shareMessage })
1885
+ shareMessage && /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground mt-3", children: shareMessage })
1357
1886
  ] });
1358
1887
  }
1359
1888
 
1360
1889
  // 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";
1890
+ import Link12 from "next/link";
1891
+ import { ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight2 } from "lucide-react";
1892
+ import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
1364
1893
  function truncateTitle(title, maxLength = 60) {
1365
1894
  return title.length > maxLength ? `${title.substring(0, maxLength)}...` : title;
1366
1895
  }
1367
- function ArticleNavigation({ previous, next, basePath }) {
1896
+ function ArticleNavigation({
1897
+ previous,
1898
+ next,
1899
+ basePath,
1900
+ fromSlug,
1901
+ pathKey,
1902
+ config
1903
+ }) {
1368
1904
  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,
1905
+ function emitStep(toSlug2, direction) {
1906
+ if (!pathKey || !fromSlug) return;
1907
+ emitArticleEvent(config == null ? void 0 : config.onEvent, {
1908
+ name: "path_step_advanced",
1909
+ pathKey,
1910
+ fromSlug,
1911
+ toSlug: toSlug2,
1912
+ direction
1913
+ });
1914
+ }
1915
+ 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: [
1916
+ /* @__PURE__ */ jsx20("div", { className: "flex-1 min-w-0", children: previous && /* @__PURE__ */ jsxs19(
1917
+ Link12,
1372
1918
  {
1373
1919
  href: `${basePath}/${previous.slug}`,
1920
+ onClick: () => emitStep(previous.slug, "previous"),
1374
1921
  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
1922
  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) })
1923
+ /* @__PURE__ */ jsx20(ChevronLeft2, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" }),
1924
+ /* @__PURE__ */ jsxs19("div", { className: "min-w-0 flex-1", children: [
1925
+ /* @__PURE__ */ jsx20("div", { className: "text-sm text-muted-foreground mb-1", children: "Previous Article" }),
1926
+ /* @__PURE__ */ jsx20("div", { className: "font-medium text-foreground truncate", title: previous.title, children: truncateTitle(previous.title) })
1380
1927
  ] })
1381
1928
  ]
1382
1929
  }
1383
1930
  ) }),
1384
- /* @__PURE__ */ jsx17("div", { className: "flex-1 min-w-0", children: next && /* @__PURE__ */ jsxs17(
1385
- Link10,
1931
+ /* @__PURE__ */ jsx20("div", { className: "flex-1 min-w-0", children: next && /* @__PURE__ */ jsxs19(
1932
+ Link12,
1386
1933
  {
1387
1934
  href: `${basePath}/${next.slug}`,
1935
+ onClick: () => emitStep(next.slug, "next"),
1388
1936
  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
1937
  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) })
1938
+ /* @__PURE__ */ jsxs19("div", { className: "min-w-0 flex-1 text-right", children: [
1939
+ /* @__PURE__ */ jsx20("div", { className: "text-sm text-muted-foreground mb-1", children: "Next Article" }),
1940
+ /* @__PURE__ */ jsx20("div", { className: "font-medium text-foreground truncate", title: next.title, children: truncateTitle(next.title) })
1393
1941
  ] }),
1394
- /* @__PURE__ */ jsx17(ChevronRight, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" })
1942
+ /* @__PURE__ */ jsx20(ChevronRight2, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" })
1395
1943
  ]
1396
1944
  }
1397
1945
  ) })
1398
1946
  ] }) });
1399
1947
  }
1400
1948
 
1949
+ // src/RelatedArticlesSection.tsx
1950
+ import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
1951
+ function RelatedArticlesSection({
1952
+ articles,
1953
+ category,
1954
+ heading,
1955
+ config,
1956
+ fromSlug,
1957
+ source = "category"
1958
+ }) {
1959
+ var _a;
1960
+ if (articles.length === 0) return null;
1961
+ return /* @__PURE__ */ jsxs20("section", { className: "mt-12 pt-8 border-t border-border", "aria-label": "Related articles", children: [
1962
+ /* @__PURE__ */ jsx21(
1963
+ "h2",
1964
+ {
1965
+ className: "text-xl font-semibold text-foreground mb-6",
1966
+ style: { fontFamily: (_a = config == null ? void 0 : config.theme) == null ? void 0 : _a.headerFontFamily },
1967
+ children: heading != null ? heading : `More in ${category}`
1968
+ }
1969
+ ),
1970
+ /* @__PURE__ */ jsx21("div", { className: "grid gap-6 sm:grid-cols-2 lg:grid-cols-3", children: articles.map((article) => /* @__PURE__ */ jsx21(
1971
+ "div",
1972
+ {
1973
+ onClickCapture: () => {
1974
+ if (!fromSlug) return;
1975
+ emitArticleEvent(config == null ? void 0 : config.onEvent, {
1976
+ name: "related_article_clicked",
1977
+ fromSlug,
1978
+ toSlug: article.slug,
1979
+ source
1980
+ });
1981
+ },
1982
+ children: /* @__PURE__ */ jsx21(ArticleCard, { article, config })
1983
+ },
1984
+ article.slug
1985
+ )) })
1986
+ ] });
1987
+ }
1988
+
1401
1989
  // src/ArticleBackLink.tsx
1402
- import Link11 from "next/link";
1990
+ import Link13 from "next/link";
1403
1991
  import { ArrowLeft } from "lucide-react";
1404
- import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
1992
+ import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
1405
1993
  function ArticleBackLink({
1406
1994
  config,
1407
1995
  href = "/articles",
@@ -1409,13 +1997,13 @@ function ArticleBackLink({
1409
1997
  className
1410
1998
  }) {
1411
1999
  if (config.showBackToArticles === false) return null;
1412
- return /* @__PURE__ */ jsxs18(
1413
- Link11,
2000
+ return /* @__PURE__ */ jsxs21(
2001
+ Link13,
1414
2002
  {
1415
2003
  href,
1416
2004
  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
2005
  children: [
1418
- /* @__PURE__ */ jsx18(ArrowLeft, { className: "h-4 w-4" }),
2006
+ /* @__PURE__ */ jsx22(ArrowLeft, { className: "h-4 w-4" }),
1419
2007
  label
1420
2008
  ]
1421
2009
  }
@@ -1423,17 +2011,17 @@ function ArticleBackLink({
1423
2011
  }
1424
2012
 
1425
2013
  // src/ArticleTOC.tsx
1426
- import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
2014
+ import { jsx as jsx23, jsxs as jsxs22 } from "react/jsx-runtime";
1427
2015
  function ArticleTOC({ toc, className }) {
1428
2016
  if (!toc.length) return null;
1429
- return /* @__PURE__ */ jsxs19(
2017
+ return /* @__PURE__ */ jsxs22(
1430
2018
  "nav",
1431
2019
  {
1432
2020
  "aria-label": "Table of contents",
1433
2021
  className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
1434
2022
  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(
2023
+ /* @__PURE__ */ jsx23("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
2024
+ /* @__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
2025
  "a",
1438
2026
  {
1439
2027
  href: `#${item.id}`,
@@ -1447,35 +2035,35 @@ function ArticleTOC({ toc, className }) {
1447
2035
  }
1448
2036
 
1449
2037
  // src/ScrollToTop.tsx
1450
- import { useEffect as useEffect4, useState as useState5 } from "react";
2038
+ import { useEffect as useEffect5, useState as useState5 } from "react";
1451
2039
  import { ArrowUp } from "lucide-react";
1452
- import { jsx as jsx20 } from "react/jsx-runtime";
2040
+ import { jsx as jsx24 } from "react/jsx-runtime";
1453
2041
  function ScrollToTop() {
1454
2042
  const [visible, setVisible] = useState5(false);
1455
- useEffect4(() => {
2043
+ useEffect5(() => {
1456
2044
  const onScroll = () => setVisible(globalThis.scrollY > 300);
1457
2045
  globalThis.addEventListener("scroll", onScroll, { passive: true });
1458
2046
  return () => globalThis.removeEventListener("scroll", onScroll);
1459
2047
  }, []);
1460
2048
  if (!visible) return null;
1461
- return /* @__PURE__ */ jsx20(
2049
+ return /* @__PURE__ */ jsx24(
1462
2050
  "button",
1463
2051
  {
1464
2052
  "aria-label": "Scroll to top",
1465
2053
  onClick: () => globalThis.scrollTo({ top: 0, behavior: "smooth" }),
1466
2054
  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" })
2055
+ children: /* @__PURE__ */ jsx24(ArrowUp, { className: "h-5 w-5" })
1468
2056
  }
1469
2057
  );
1470
2058
  }
1471
2059
 
1472
2060
  // src/CommentsSection.tsx
1473
2061
  import { useSession, signIn } from "next-auth/react";
1474
- import { useCallback as useCallback2, useEffect as useEffect5, useState as useState8 } from "react";
2062
+ import { useCallback as useCallback2, useEffect as useEffect6, useState as useState8 } from "react";
1475
2063
 
1476
2064
  // src/CommentForm.tsx
1477
2065
  import { useState as useState6 } from "react";
1478
- import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
2066
+ import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
1479
2067
  var MAX_LENGTH = 2e3;
1480
2068
  var WARN_THRESHOLD = 1800;
1481
2069
  function submitLabel(submitting, parentId) {
@@ -1514,8 +2102,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1514
2102
  }
1515
2103
  });
1516
2104
  }
1517
- return /* @__PURE__ */ jsxs20("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
1518
- /* @__PURE__ */ jsx21(
2105
+ return /* @__PURE__ */ jsxs23("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
2106
+ /* @__PURE__ */ jsx25(
1519
2107
  "textarea",
1520
2108
  {
1521
2109
  value: body,
@@ -1528,13 +2116,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1528
2116
  "aria-label": parentId ? "Reply text" : "Comment text"
1529
2117
  }
1530
2118
  ),
1531
- remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ jsxs20("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
2119
+ remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ jsxs23("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
1532
2120
  remaining,
1533
2121
  " characters remaining"
1534
2122
  ] }),
1535
- error && /* @__PURE__ */ jsx21("p", { className: "text-xs text-destructive", children: error }),
1536
- /* @__PURE__ */ jsxs20("div", { className: "flex gap-2", children: [
1537
- /* @__PURE__ */ jsx21(
2123
+ error && /* @__PURE__ */ jsx25("p", { className: "text-xs text-destructive", children: error }),
2124
+ /* @__PURE__ */ jsxs23("div", { className: "flex gap-2", children: [
2125
+ /* @__PURE__ */ jsx25(
1538
2126
  "button",
1539
2127
  {
1540
2128
  type: "submit",
@@ -1543,7 +2131,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1543
2131
  children: submitLabel(submitting, parentId)
1544
2132
  }
1545
2133
  ),
1546
- onCancel && /* @__PURE__ */ jsx21(
2134
+ onCancel && /* @__PURE__ */ jsx25(
1547
2135
  "button",
1548
2136
  {
1549
2137
  type: "button",
@@ -1558,7 +2146,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
1558
2146
 
1559
2147
  // src/CommentItem.tsx
1560
2148
  import { useState as useState7 } from "react";
1561
- import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
2149
+ import { Fragment as Fragment5, jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
1562
2150
  function relativeTime(iso) {
1563
2151
  const diff = Date.now() - new Date(iso).getTime();
1564
2152
  const minutes = Math.floor(diff / 6e4);
@@ -1596,15 +2184,15 @@ function CommentItem({
1596
2184
  }
1597
2185
  });
1598
2186
  }
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) })
2187
+ return /* @__PURE__ */ jsxs24("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
2188
+ /* @__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: [
2189
+ /* @__PURE__ */ jsxs24("div", { className: "flex items-center justify-between gap-2", children: [
2190
+ /* @__PURE__ */ jsx26("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
2191
+ /* @__PURE__ */ jsx26("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
1604
2192
  ] }),
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(
2193
+ /* @__PURE__ */ jsx26("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
2194
+ /* @__PURE__ */ jsxs24("div", { className: "flex gap-3 pt-1", children: [
2195
+ canReply && /* @__PURE__ */ jsx26(
1608
2196
  "button",
1609
2197
  {
1610
2198
  onClick: () => setShowReplyForm((v) => !v),
@@ -1612,7 +2200,7 @@ function CommentItem({
1612
2200
  children: showReplyForm ? "Cancel" : "Reply"
1613
2201
  }
1614
2202
  ),
1615
- canDelete && /* @__PURE__ */ jsx22(
2203
+ canDelete && /* @__PURE__ */ jsx26(
1616
2204
  "button",
1617
2205
  {
1618
2206
  onClick: handleDelete,
@@ -1623,7 +2211,7 @@ function CommentItem({
1623
2211
  )
1624
2212
  ] })
1625
2213
  ] }) }),
1626
- showReplyForm && /* @__PURE__ */ jsx22("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ jsx22(
2214
+ showReplyForm && /* @__PURE__ */ jsx26("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ jsx26(
1627
2215
  CommentForm,
1628
2216
  {
1629
2217
  articleSlug,
@@ -1635,7 +2223,7 @@ function CommentItem({
1635
2223
  onCancel: () => setShowReplyForm(false)
1636
2224
  }
1637
2225
  ) }),
1638
- replies.length > 0 && /* @__PURE__ */ jsx22("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ jsx22(
2226
+ replies.length > 0 && /* @__PURE__ */ jsx26("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ jsx26(
1639
2227
  CommentItem,
1640
2228
  {
1641
2229
  comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
@@ -1650,7 +2238,7 @@ function CommentItem({
1650
2238
  }
1651
2239
 
1652
2240
  // src/CommentThread.tsx
1653
- import { jsx as jsx23 } from "react/jsx-runtime";
2241
+ import { jsx as jsx27 } from "react/jsx-runtime";
1654
2242
  function CommentThread({
1655
2243
  comments,
1656
2244
  articleSlug,
@@ -1658,9 +2246,9 @@ function CommentThread({
1658
2246
  onRefresh
1659
2247
  }) {
1660
2248
  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." });
2249
+ 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
2250
  }
1663
- return /* @__PURE__ */ jsx23("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ jsx23(
2251
+ return /* @__PURE__ */ jsx27("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ jsx27(
1664
2252
  CommentItem,
1665
2253
  {
1666
2254
  comment,
@@ -1673,7 +2261,7 @@ function CommentThread({
1673
2261
  }
1674
2262
 
1675
2263
  // src/CommentsSection.tsx
1676
- import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
2264
+ import { jsx as jsx28, jsxs as jsxs25 } from "react/jsx-runtime";
1677
2265
  function CommentsSection({ articleSlug, config }) {
1678
2266
  var _a, _b, _c, _d, _e, _f;
1679
2267
  const { data: session, status } = useSession();
@@ -1697,7 +2285,7 @@ function CommentsSection({ articleSlug, config }) {
1697
2285
  setLoading(false);
1698
2286
  }
1699
2287
  }), [articleSlug]);
1700
- useEffect5(() => {
2288
+ useEffect6(() => {
1701
2289
  if (enabled) {
1702
2290
  fetchComments().catch(() => void 0);
1703
2291
  }
@@ -1705,10 +2293,10 @@ function CommentsSection({ articleSlug, config }) {
1705
2293
  if (!enabled) return null;
1706
2294
  const count = comments.length;
1707
2295
  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(
2296
+ return /* @__PURE__ */ jsxs25("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
2297
+ /* @__PURE__ */ jsx28("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
2298
+ status === "authenticated" ? /* @__PURE__ */ jsx28(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ jsxs25("p", { className: "text-sm text-muted-foreground", children: [
2299
+ /* @__PURE__ */ jsx28(
1712
2300
  "button",
1713
2301
  {
1714
2302
  onClick: () => signIn(),
@@ -1719,8 +2307,8 @@ function CommentsSection({ articleSlug, config }) {
1719
2307
  " ",
1720
2308
  "to join the discussion."
1721
2309
  ] }),
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(
2310
+ fetchError && /* @__PURE__ */ jsx28("p", { className: "text-sm text-destructive", children: fetchError }),
2311
+ loading ? /* @__PURE__ */ jsx28("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ jsx28(
1724
2312
  CommentThread,
1725
2313
  {
1726
2314
  comments,
@@ -1738,10 +2326,10 @@ export {
1738
2326
  ArticleDetailHero,
1739
2327
  ArticleNavigation,
1740
2328
  ArticleSEO,
1741
- ArticleSchema,
1742
2329
  ArticleSearchBar,
1743
2330
  ArticleSocialShare,
1744
2331
  ArticleTOC,
2332
+ ArticleViewTracker,
1745
2333
  ArticlesHero,
1746
2334
  ArticlesPage,
1747
2335
  AuthorArticlesPage,
@@ -1756,6 +2344,7 @@ export {
1756
2344
  CommentItem,
1757
2345
  CommentThread,
1758
2346
  CommentsSection,
2347
+ CtaViewTracker,
1759
2348
  DEFAULT_CATEGORIES_PAGE_SIZE,
1760
2349
  DEFAULT_LAYOUT,
1761
2350
  DEFAULT_PAGE_SIZE,
@@ -1763,7 +2352,10 @@ export {
1763
2352
  FeaturedArticle,
1764
2353
  LatestArticles,
1765
2354
  LatestArticlesSection,
2355
+ PaginationNav,
2356
+ RelatedArticlesSection,
1766
2357
  ScrollToTop,
2358
+ SeriesArticlesPage,
1767
2359
  breadcrumbsAreEnabled,
1768
2360
  getBreadcrumbsConfig,
1769
2361
  useArticles