@contentgrowth/content-widget 1.3.2 → 1.3.4

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.
package/README.md CHANGED
@@ -268,6 +268,37 @@ const widget = new ContentGrowthWidget(container, {
268
268
  />
269
269
  ```
270
270
 
271
+ **FeaturedCard:**
272
+
273
+ A compact card displaying the Featured Summary with customizable styling. Perfect for landing pages.
274
+
275
+ ```astro
276
+ <FeaturedCard
277
+ apiKey="pk_your_key_here"
278
+ category="announce"
279
+ linkPattern="/articles/{slug}"
280
+ layout="horizontal"
281
+ borderStyle="dashed"
282
+ itemsBackground="#f3f4f6"
283
+ ctaText="Read full story"
284
+ />
285
+ ```
286
+
287
+ | Prop | Type | Default | Description |
288
+ |------|------|---------|-------------|
289
+ | `apiKey` | string | - | Your API key |
290
+ | `category` | string | - | Filter by category |
291
+ | `tags` | string[] | [] | Filter by tags |
292
+ | `layout` | 'vertical' \| 'horizontal' | auto | Card layout (auto uses article setting) |
293
+ | `borderStyle` | 'none' \| 'line' \| 'dashed' | 'none' | Card border style |
294
+ | `borderColor` | string | '#e5e7eb' | Border color (CSS value) |
295
+ | `cardBackground` | string | 'none' | Card background ('none' = transparent) |
296
+ | `itemsBackground` | string | '#f3f4f6' | Background for list/quote section |
297
+ | `ctaText` | string | 'Read full story' | Call-to-action text |
298
+ | `showAuthor` | boolean | false | Show author name |
299
+ | `showReadingTime` | boolean | false | Show reading time |
300
+ | `linkPattern` | string | '/articles/{slug}' | URL pattern for link |
301
+
271
302
  ## API Client
272
303
 
273
304
  ### ContentGrowthClient
@@ -0,0 +1,191 @@
1
+ ---
2
+ import { marked } from 'marked';
3
+ import { ContentGrowthClient } from '../core/client';
4
+ import type { FeaturedContentProps } from '../types';
5
+
6
+ interface FeaturedCardProps extends Omit<FeaturedContentProps, 'showBackButton' | 'backUrl'> {
7
+ /**
8
+ * URL pattern for the article link
9
+ * Supports placeholders: {uuid}, {slug}, {category}
10
+ * @default '/articles/{slug}'
11
+ */
12
+ linkPattern?: string;
13
+
14
+ /**
15
+ * Show reading time
16
+ * @default false
17
+ */
18
+ showReadingTime?: boolean;
19
+
20
+ /**
21
+ * Show author
22
+ * @default false
23
+ */
24
+ showAuthor?: boolean;
25
+
26
+ /**
27
+ * Text for the Call to Action link
28
+ * @default "Read full story"
29
+ */
30
+ ctaText?: string;
31
+
32
+ /**
33
+ * Layout override (defaults to article settings)
34
+ */
35
+ layout?: 'vertical' | 'horizontal';
36
+
37
+ /**
38
+ * Border style
39
+ * @default 'none'
40
+ */
41
+ borderStyle?: 'none' | 'line' | 'dashed';
42
+
43
+ /**
44
+ * Border color (CSS color value)
45
+ * @default '#e5e7eb'
46
+ */
47
+ borderColor?: string;
48
+
49
+ /**
50
+ * Card background color (CSS color value, 'none' for transparent)
51
+ * @default 'none'
52
+ */
53
+ cardBackground?: string;
54
+
55
+ /**
56
+ * Background color for list/quote section (the items area)
57
+ * @default '#f3f4f6'
58
+ */
59
+ itemsBackground?: string;
60
+ }
61
+
62
+ type Props = FeaturedCardProps & { class?: string };
63
+
64
+ const {
65
+ apiKey,
66
+ baseUrl,
67
+ tags = [],
68
+ category,
69
+ excludeTags = [],
70
+ showCategory = true,
71
+ showReadingTime = false,
72
+ showAuthor = false,
73
+ ctaText: propCtaText,
74
+ linkPattern = '/articles/{slug}',
75
+ layout: propLayout,
76
+ borderStyle = 'none',
77
+ borderColor = '#e5e7eb',
78
+ cardBackground = 'none',
79
+ itemsBackground = '#f3f4f6',
80
+ class: className = ''
81
+ } = Astro.props;
82
+
83
+ // Fetch featured article
84
+ const client = new ContentGrowthClient({ apiKey, baseUrl });
85
+ let article = null;
86
+ let error = null;
87
+
88
+ try {
89
+ article = await client.getFeaturedArticle({
90
+ tags,
91
+ category,
92
+ excludeTags
93
+ });
94
+ } catch (e) {
95
+ // If 404, article remains null
96
+ console.error('Failed to fetch featured article:', e);
97
+ error = e instanceof Error ? e.message : 'Failed to load featured article';
98
+ }
99
+
100
+ // Generate article URL
101
+ const getArticleUrl = (article: any) => {
102
+ if (!article) return '#';
103
+ return linkPattern
104
+ .replace('{uuid}', article.uuid || '')
105
+ .replace('{slug}', article.slug || article.uuid || '')
106
+ .replace('{category}', article.category || 'uncategorized');
107
+ };
108
+
109
+ // Render featured summary (or fallback to regular summary) as HTML
110
+ const getSummaryHtml = (article: any) => {
111
+ const summaryText = article.featuredSummary || article.summary;
112
+ if (!summaryText) return '';
113
+ return marked.parse(summaryText, { async: false }) as string;
114
+ };
115
+
116
+ const layout = propLayout || article?.featuredSummaryLayout || 'standard';
117
+ const layoutClass = layout !== 'standard' ? `cg-layout-${layout}` : '';
118
+ const borderClass = borderStyle !== 'none' ? `cg-border-${borderStyle}` : '';
119
+
120
+ // Use ctaText from prop, or from article data, or fallback to default
121
+ const ctaText = propCtaText || article?.featuredCtaText || 'Read full story';
122
+
123
+ // Custom CSS properties for styling
124
+ const customStyles = [
125
+ borderColor !== '#e5e7eb' ? `--cg-card-border-color: ${borderColor}` : '',
126
+ cardBackground !== 'none' ? `--cg-card-bg: ${cardBackground}` : '',
127
+ itemsBackground !== '#f3f4f6' ? `--cg-items-bg: ${itemsBackground}` : '',
128
+ ].filter(Boolean).join('; ');
129
+ ---
130
+
131
+ {article ? (
132
+ <a
133
+ href={getArticleUrl(article)}
134
+ class={`cg-widget cg-featured-card ${className} ${layoutClass} ${borderClass}`}
135
+ style={customStyles || undefined}
136
+ data-cg-widget="featured-card"
137
+ >
138
+ <article class="cg-featured-card-inner">
139
+ {/* Header with category badge */}
140
+ {showCategory && article.category && (
141
+ <div class="cg-featured-card-category">
142
+ <span class="cg-category-badge">{article.category}</span>
143
+ </div>
144
+ )}
145
+
146
+ {/* Title */}
147
+ <h3 class="cg-featured-card-title">{article.title}</h3>
148
+
149
+ {/* Featured Summary */}
150
+ {(article.featuredSummary || article.summary) && (
151
+ <div
152
+ class="cg-featured-card-summary"
153
+ set:html={getSummaryHtml(article)}
154
+ />
155
+ )}
156
+
157
+ {/* Footer with meta info */}
158
+ {(showAuthor || showReadingTime) && (
159
+ <div class="cg-featured-card-footer">
160
+ {showAuthor && <span class="cg-featured-card-author">{article.authorName}</span>}
161
+
162
+ {showAuthor && showReadingTime && (
163
+ <span class="cg-featured-card-separator">•</span>
164
+ )}
165
+
166
+ {showReadingTime && (
167
+ <span class="cg-featured-card-reading-time">
168
+ {Math.ceil(article.wordCount / 200)} min read
169
+ </span>
170
+ )}
171
+ </div>
172
+ )}
173
+
174
+ {/* Read more indicator */}
175
+ <div class="cg-featured-card-cta">
176
+ <span>{ctaText}</span>
177
+ <svg class="cg-featured-card-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="16" height="16">
178
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
179
+ </svg>
180
+ </div>
181
+ </article>
182
+ </a>
183
+ ) : (
184
+ error ? (
185
+ <div class={`cg-widget cg-error ${className}`}>
186
+ {error}
187
+ </div>
188
+ ) : null
189
+ )}
190
+
191
+
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+ import type { FeaturedContentProps } from '../types';
3
+ interface FeaturedCardProps extends Omit<FeaturedContentProps, 'showBackButton' | 'backUrl' | 'showAiSummary' | 'showTags'> {
4
+ /**
5
+ * URL pattern for the article link
6
+ * Supports placeholders: {uuid}, {slug}, {category}
7
+ * @default '/articles/{slug}'
8
+ */
9
+ linkPattern?: string;
10
+ /**
11
+ * Show reading time
12
+ * @default false
13
+ */
14
+ showReadingTime?: boolean;
15
+ /**
16
+ * Show author
17
+ * @default false
18
+ */
19
+ showAuthor?: boolean;
20
+ /**
21
+ * Text for the Call to Action link
22
+ * @default "Read full story"
23
+ */
24
+ ctaText?: string;
25
+ /**
26
+ * Link target attribute
27
+ * @default undefined (same tab)
28
+ */
29
+ linkTarget?: string;
30
+ /**
31
+ * Layout override
32
+ */
33
+ layout?: 'vertical' | 'horizontal';
34
+ /**
35
+ * Border style
36
+ * @default 'none'
37
+ */
38
+ borderStyle?: 'none' | 'line' | 'dashed';
39
+ /**
40
+ * Border color (CSS color value)
41
+ * @default '#e5e7eb'
42
+ */
43
+ borderColor?: string;
44
+ /**
45
+ * Card background color (CSS color value, 'none' for transparent)
46
+ * @default 'none'
47
+ */
48
+ cardBackground?: string;
49
+ /**
50
+ * Background color for list/quote section (the items area)
51
+ * @default '#f3f4f6'
52
+ */
53
+ itemsBackground?: string;
54
+ }
55
+ export declare const FeaturedCard: React.FC<FeaturedCardProps>;
56
+ export {};
57
+ //# sourceMappingURL=FeaturedCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FeaturedCard.d.ts","sourceRoot":"","sources":["../../src/react/FeaturedCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,KAAK,EAAE,oBAAoB,EAAsB,MAAM,UAAU,CAAC;AAEzE,UAAU,iBAAkB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,gBAAgB,GAAG,SAAS,GAAG,eAAe,GAAG,UAAU,CAAC;IACvH;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IAEnC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAEzC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA8IpD,CAAC"}
@@ -0,0 +1,80 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { marked } from 'marked';
3
+ import { ContentGrowthClient } from '../core/client';
4
+ export const FeaturedCard = ({ apiKey, baseUrl, tags = [], category, excludeTags = [], showCategory = true, showReadingTime = false, showAuthor = false, ctaText: propCtaText, linkPattern = '/articles/{slug}', linkTarget, layout: propLayout, borderStyle = 'none', borderColor = '#e5e7eb', cardBackground = 'none', itemsBackground = '#f3f4f6', className = '' }) => {
5
+ const [article, setArticle] = useState(null);
6
+ const [loading, setLoading] = useState(true);
7
+ const [error, setError] = useState(null);
8
+ useEffect(() => {
9
+ const fetchArticle = async () => {
10
+ setLoading(true);
11
+ try {
12
+ const client = new ContentGrowthClient({ apiKey, baseUrl });
13
+ const fetchedArticle = await client.getFeaturedArticle({
14
+ tags,
15
+ category,
16
+ excludeTags
17
+ });
18
+ setArticle(fetchedArticle);
19
+ }
20
+ catch (err) {
21
+ setError(err instanceof Error ? err.message : 'Failed to load featured article');
22
+ }
23
+ finally {
24
+ setLoading(false);
25
+ }
26
+ };
27
+ fetchArticle();
28
+ }, [apiKey, baseUrl, category, JSON.stringify(tags), JSON.stringify(excludeTags)]);
29
+ // Generate article URL
30
+ const getArticleUrl = (article) => {
31
+ return linkPattern
32
+ .replace('{uuid}', article.uuid || '')
33
+ .replace('{slug}', article.slug || article.uuid || '')
34
+ .replace('{category}', article.category || 'uncategorized');
35
+ };
36
+ // Render featured summary (or fallback to regular summary) as HTML
37
+ const getSummaryHtml = (article) => {
38
+ const summaryText = article.featuredSummary || article.summary;
39
+ if (!summaryText)
40
+ return '';
41
+ return marked.parse(summaryText, { async: false });
42
+ };
43
+ if (loading) {
44
+ return (React.createElement("div", { className: `cg-widget cg-loading ${className}` },
45
+ React.createElement("div", { className: "cg-spinner" })));
46
+ }
47
+ if (error || !article) {
48
+ return (React.createElement("div", { className: `cg-widget cg-error ${className}` }, error || 'No featured content found'));
49
+ }
50
+ const summaryHtml = getSummaryHtml(article);
51
+ // Explicitly use any to access dynamic properties until types are fully updated
52
+ const layout = propLayout || article.featuredSummaryLayout || 'standard';
53
+ const readingTime = Math.ceil(article.wordCount / 200);
54
+ const borderClass = borderStyle !== 'none' ? `cg-border-${borderStyle}` : '';
55
+ // Use ctaText from prop, or from article data, or fallback to default
56
+ const ctaText = propCtaText || article.featuredCtaText || 'Read full story';
57
+ const customStyles = {};
58
+ if (borderColor !== '#e5e7eb')
59
+ customStyles['--cg-card-border-color'] = borderColor;
60
+ if (cardBackground !== 'none')
61
+ customStyles['--cg-card-bg'] = cardBackground;
62
+ if (itemsBackground !== '#f3f4f6')
63
+ customStyles['--cg-items-bg'] = itemsBackground;
64
+ return (React.createElement("a", { href: getArticleUrl(article), className: `cg-widget cg-featured-card ${className} ${layout !== 'standard' ? `cg-layout-${layout}` : ''} ${borderClass}`, style: Object.keys(customStyles).length > 0 ? customStyles : undefined, "data-cg-widget": "featured-card", target: linkTarget, rel: linkTarget === '_blank' ? 'noopener noreferrer' : undefined },
65
+ React.createElement("article", { className: "cg-featured-card-inner" },
66
+ showCategory && article.category && (React.createElement("div", { className: "cg-featured-card-category" },
67
+ React.createElement("span", { className: "cg-category-badge" }, article.category))),
68
+ React.createElement("h3", { className: "cg-featured-card-title" }, article.title),
69
+ summaryHtml && (React.createElement("div", { className: "cg-featured-card-summary", dangerouslySetInnerHTML: { __html: summaryHtml } })),
70
+ (showAuthor || showReadingTime) && (React.createElement("div", { className: "cg-featured-card-footer" },
71
+ showAuthor && React.createElement("span", { className: "cg-featured-card-author" }, article.authorName),
72
+ showAuthor && showReadingTime && (React.createElement("span", { className: "cg-featured-card-separator" }, "\u2022")),
73
+ showReadingTime && (React.createElement("span", { className: "cg-featured-card-reading-time" },
74
+ readingTime,
75
+ " min read")))),
76
+ React.createElement("div", { className: "cg-featured-card-cta" },
77
+ React.createElement("span", null, ctaText),
78
+ React.createElement("svg", { className: "cg-featured-card-arrow", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", width: "16", height: "16" },
79
+ React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M9 5l7 7-7 7" }))))));
80
+ };
@@ -4,6 +4,7 @@
4
4
  export { ContentList } from './ContentList.js';
5
5
  export { ContentViewer } from './ContentViewer.js';
6
6
  export { FeaturedContent } from './FeaturedContent.js';
7
+ export { FeaturedCard } from './FeaturedCard.js';
7
8
  export * from './hooks.js';
8
9
  export type { ReactContentListProps } from './ContentList.js';
9
10
  export type { ReactContentViewerProps } from './ContentViewer.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,cAAc,YAAY,CAAC;AAE3B,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,YAAY,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,cAAc,YAAY,CAAC;AAE3B,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,YAAY,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -4,4 +4,5 @@
4
4
  export { ContentList } from './ContentList.js';
5
5
  export { ContentViewer } from './ContentViewer.js';
6
6
  export { FeaturedContent } from './FeaturedContent.js';
7
+ export { FeaturedCard } from './FeaturedCard.js';
7
8
  export * from './hooks.js';
package/dist/styles.css CHANGED
@@ -19,7 +19,7 @@
19
19
  --cg-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
20
20
  --cg-radius: 12px;
21
21
  --cg-transition: all 0.2s ease;
22
-
22
+
23
23
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
24
24
  font-size: 16px;
25
25
  line-height: 1.5;
@@ -166,7 +166,7 @@
166
166
  grid-template-columns: 1fr;
167
167
  grid-template-rows: auto auto;
168
168
  }
169
-
169
+
170
170
  .cg-content-rows .cg-expand-btn {
171
171
  grid-column: 1;
172
172
  grid-row: 1;
@@ -635,7 +635,9 @@ a.cg-card {
635
635
  }
636
636
 
637
637
  @keyframes cg-spin {
638
- to { transform: rotate(360deg); }
638
+ to {
639
+ transform: rotate(360deg);
640
+ }
639
641
  }
640
642
 
641
643
  .cg-loading p,
@@ -968,3 +970,262 @@ a.cg-card {
968
970
  color: var(--cg-text-secondary);
969
971
  font-size: 1.125rem;
970
972
  }
973
+
974
+ /* ===== Featured Card Component ===== */
975
+ .cg-featured-card {
976
+ /* Custom property defaults */
977
+ --cg-card-border-color: #e5e7eb;
978
+ --cg-card-bg: transparent;
979
+ --cg-items-bg: #f3f4f6;
980
+
981
+ display: block;
982
+ text-decoration: none;
983
+ color: inherit;
984
+ border-radius: 12px;
985
+ background: var(--cg-card-bg);
986
+ border: none;
987
+ transition: all 0.2s ease;
988
+ overflow: hidden;
989
+ }
990
+
991
+ /* Border style variants */
992
+ .cg-featured-card.cg-border-line {
993
+ border: 1px solid var(--cg-card-border-color);
994
+ }
995
+
996
+ .cg-featured-card.cg-border-dashed {
997
+ border: 2px dashed var(--cg-card-border-color);
998
+ }
999
+
1000
+ .cg-featured-card:hover {
1001
+ border-color: var(--cg-primary);
1002
+ }
1003
+
1004
+ .cg-featured-card-inner {
1005
+ padding: 2rem 2.5rem;
1006
+ height: 100%;
1007
+ display: flex;
1008
+ flex-direction: column;
1009
+ }
1010
+
1011
+ .cg-featured-card-category {
1012
+ margin-bottom: 1.25rem;
1013
+ }
1014
+
1015
+ .cg-category-badge {
1016
+ display: inline-block;
1017
+ padding: 0.25rem 0.75rem;
1018
+ background: #eff6ff;
1019
+ color: var(--cg-primary);
1020
+ border-radius: 4px;
1021
+ font-size: 0.75rem;
1022
+ font-weight: 600;
1023
+ }
1024
+
1025
+ .cg-featured-card-title {
1026
+ margin: 0 0 1rem;
1027
+ font-size: 1.625rem;
1028
+ font-weight: 800;
1029
+ line-height: 1.25;
1030
+ color: var(--cg-text);
1031
+ letter-spacing: -0.02em;
1032
+ }
1033
+
1034
+ .cg-featured-card-summary {
1035
+ font-size: 1rem;
1036
+ line-height: 1.7;
1037
+ color: var(--cg-text-secondary);
1038
+ margin-bottom: 1.5rem;
1039
+ flex-grow: 1;
1040
+ }
1041
+
1042
+ .cg-featured-card-summary p {
1043
+ margin: 0 0 1rem;
1044
+ }
1045
+
1046
+ .cg-featured-card-summary p:last-child {
1047
+ margin: 0;
1048
+ }
1049
+
1050
+ .cg-featured-card-summary strong,
1051
+ .cg-featured-card-summary b {
1052
+ font-weight: 700;
1053
+ color: var(--cg-text);
1054
+ }
1055
+
1056
+ .cg-featured-card-footer {
1057
+ display: flex;
1058
+ align-items: center;
1059
+ font-size: 0.875rem;
1060
+ font-weight: 500;
1061
+ color: var(--cg-text-secondary);
1062
+ margin-bottom: 1rem;
1063
+ }
1064
+
1065
+ .cg-featured-card-separator {
1066
+ margin: 0 0.5rem;
1067
+ opacity: 0.4;
1068
+ }
1069
+
1070
+ /* CTA: Simple text link style */
1071
+ .cg-featured-card-cta {
1072
+ display: inline-flex;
1073
+ align-items: center;
1074
+ gap: 0.375rem;
1075
+ font-size: 1rem;
1076
+ font-weight: 600;
1077
+ color: var(--cg-primary);
1078
+ text-decoration: none;
1079
+ transition: all 0.2s ease;
1080
+ align-self: flex-start;
1081
+ }
1082
+
1083
+ .cg-featured-card:hover .cg-featured-card-cta {
1084
+ gap: 0.625rem;
1085
+ text-decoration: underline;
1086
+ }
1087
+
1088
+ .cg-featured-card-arrow {
1089
+ transition: transform 0.2s ease;
1090
+ }
1091
+
1092
+ .cg-featured-card:hover .cg-featured-card-arrow {
1093
+ transform: translateX(3px);
1094
+ }
1095
+
1096
+ /* ===== Featured Summary Template Styles ===== */
1097
+
1098
+ /* Intro paragraph styling */
1099
+ .cg-featured-card-summary>p:first-child {
1100
+ font-size: 1rem;
1101
+ line-height: 1.7;
1102
+ }
1103
+
1104
+ /* List & Steps Template - Styled like reference */
1105
+ .cg-featured-card-summary ul,
1106
+ .cg-featured-card-summary ol {
1107
+ margin: 0;
1108
+ padding: 1.25rem 1.5rem;
1109
+ padding-left: 1.5rem;
1110
+ list-style: none;
1111
+ background: var(--cg-items-bg);
1112
+ border-radius: 8px;
1113
+ }
1114
+
1115
+ .cg-featured-card-summary li {
1116
+ margin-bottom: 1.25rem;
1117
+ padding-left: 2.5rem;
1118
+ line-height: 1.5;
1119
+ position: relative;
1120
+ }
1121
+
1122
+ .cg-featured-card-summary li:last-child {
1123
+ margin-bottom: 0;
1124
+ }
1125
+
1126
+ /* Unordered list: simple bullets */
1127
+ .cg-featured-card-summary ul li::before {
1128
+ content: '';
1129
+ position: absolute;
1130
+ left: 0.5rem;
1131
+ top: 0.5rem;
1132
+ width: 6px;
1133
+ height: 6px;
1134
+ background: var(--cg-primary);
1135
+ border-radius: 50%;
1136
+ }
1137
+
1138
+ /* Ordered list: numbered circles like reference */
1139
+ .cg-featured-card-summary ol {
1140
+ counter-reset: item;
1141
+ }
1142
+
1143
+ .cg-featured-card-summary ol li::before {
1144
+ content: counter(item);
1145
+ counter-increment: item;
1146
+ position: absolute;
1147
+ left: 0;
1148
+ top: 0;
1149
+ width: 1.5rem;
1150
+ height: 1.5rem;
1151
+ background: var(--cg-primary);
1152
+ color: white;
1153
+ border-radius: 4px;
1154
+ font-size: 0.75rem;
1155
+ font-weight: 700;
1156
+ display: flex;
1157
+ align-items: center;
1158
+ justify-content: center;
1159
+ }
1160
+
1161
+ /* List item content: bold title + description on next line */
1162
+ .cg-featured-card-summary li strong {
1163
+ display: block;
1164
+ font-size: 0.9375rem;
1165
+ font-weight: 700;
1166
+ color: var(--cg-text);
1167
+ margin-bottom: 0.125rem;
1168
+ }
1169
+
1170
+ /* Quote Template */
1171
+ .cg-featured-card-summary blockquote {
1172
+ margin: 0;
1173
+ padding: 1rem 1.25rem;
1174
+ border-left: 3px solid var(--cg-primary);
1175
+ background: var(--cg-bg-secondary);
1176
+ border-radius: 0 8px 8px 0;
1177
+ font-style: italic;
1178
+ font-size: 1rem;
1179
+ color: var(--cg-text);
1180
+ }
1181
+
1182
+ /* ===== Horizontal Layout ===== */
1183
+ /* Horizontal layout: intro paragraph on left, list/remaining content on right */
1184
+
1185
+ @media (min-width: 768px) {
1186
+ .cg-layout-horizontal .cg-featured-card-inner {
1187
+ padding: 2.5rem 3rem;
1188
+ }
1189
+
1190
+ .cg-layout-horizontal .cg-featured-card-title {
1191
+ font-size: 1.875rem;
1192
+ }
1193
+
1194
+ /* Only the summary content gets the horizontal grid */
1195
+ .cg-layout-horizontal .cg-featured-card-summary {
1196
+ display: grid;
1197
+ grid-template-columns: 1fr 1.25fr;
1198
+ gap: 2.5rem;
1199
+ align-items: stretch;
1200
+ /* Changed from start to stretch for balance */
1201
+ }
1202
+
1203
+ /* First paragraph (intro) stays on left - improved styling */
1204
+ .cg-layout-horizontal .cg-featured-card-summary>p:first-child {
1205
+ grid-column: 1;
1206
+ grid-row: 1;
1207
+ margin: 0;
1208
+ font-size: 1.0625rem;
1209
+ line-height: 1.8;
1210
+ color: var(--cg-text-secondary);
1211
+ display: flex;
1212
+ flex-direction: column;
1213
+ justify-content: center;
1214
+ /* Vertically center the text */
1215
+ }
1216
+
1217
+ /* List/quote goes on right */
1218
+ .cg-layout-horizontal .cg-featured-card-summary ul,
1219
+ .cg-layout-horizontal .cg-featured-card-summary ol,
1220
+ .cg-layout-horizontal .cg-featured-card-summary blockquote {
1221
+ grid-column: 2;
1222
+ grid-row: 1;
1223
+ margin: 0;
1224
+ }
1225
+
1226
+ /* If no list, additional paragraphs go on right */
1227
+ .cg-layout-horizontal .cg-featured-card-summary>p:not(:first-child) {
1228
+ grid-column: 2;
1229
+ margin: 0 0 0.75rem;
1230
+ }
1231
+ }
@@ -12,6 +12,7 @@ export interface Article {
12
12
  authorName: string;
13
13
  publishedAt: number;
14
14
  summary: string | null;
15
+ featuredSummary: string | null;
15
16
  tags: string[];
16
17
  wordCount: number;
17
18
  }