diff --git a/app/(marketing)/learn/page.tsx b/app/(marketing)/learn/page.tsx
index 04a63dd..045a95f 100644
--- a/app/(marketing)/learn/page.tsx
+++ b/app/(marketing)/learn/page.tsx
@@ -32,6 +32,20 @@ export const metadata: Metadata = routeMetadata("/learn", {
const CASE_STUDY_TILE_RADIUS_CLASS = "rounded-[23.093px]";
+/** Figma card grid: 1 column below 640, 2 from 640, 3 from 1024, 4 from 1440. */
+const ARTICLE_GRID_CLASS = [
+ "flex flex-col gap-[var(--space-050)]",
+ "md:flex-row md:flex-wrap md:justify-center md:gap-[var(--space-400)]",
+ "md:px-[var(--space-1200)] md:py-[var(--space-600)]",
+ "lg:px-[var(--space-1800)] lg:pt-[var(--space-800)] lg:pb-[var(--space-1800)]",
+ "[&>*]:min-w-0 [&>*]:w-full",
+ "md:[&>*]:w-[calc((100%-var(--space-400))/2)]",
+ "lg:[&>*]:w-[calc((100%-2*var(--space-400))/3)]",
+ "xl:[&>*]:w-[calc((100%-3*var(--space-400))/4)]",
+].join(" ");
+
+const GUIDE_TAG_SRC = "/content/blog/how-communityrule-works-tag.svg";
+
const CASE_STUDY_LINK_CLASS = [
CASE_STUDY_TILE_RADIUS_CLASS,
"block shrink-0 cursor-pointer outline-none transition-[opacity,transform] duration-200",
@@ -163,14 +177,14 @@ export default function LearnPage() {
{articlePosts.map((post) => (
diff --git a/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.container.tsx b/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.container.tsx
index 0391318..f6626ea 100644
--- a/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.container.tsx
+++ b/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.container.tsx
@@ -24,6 +24,7 @@ const ContentThumbnailTemplateContainer = memo
(
showByline = true,
showLeadingImage = true,
leadingImageAlt,
+ leadingImageSrc,
}) => {
const variant = variantProp;
const sizing = sizingProp;
@@ -56,7 +57,7 @@ const ContentThumbnailTemplateContainer = memo(
};
// For "responsive", emit both orientations so the source can
- // swap at smd without a second card in the DOM.
+ // swap at md (640px) without a second card in the DOM.
const backgroundImage =
variant === "responsive"
? getBackgroundImage(post, "horizontal")
@@ -78,6 +79,7 @@ const ContentThumbnailTemplateContainer = memo(
showByline={showByline}
showLeadingImage={showLeadingImage}
leadingImageAlt={leadingImageAlt}
+ leadingImageSrc={leadingImageSrc}
/>
);
},
diff --git a/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.types.ts b/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.types.ts
index b98b52c..5958fa3 100644
--- a/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.types.ts
+++ b/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.types.ts
@@ -11,8 +11,8 @@ export interface ContentThumbnailTemplateProps {
post: BlogPost;
className?: string;
/**
- * vertical | horizontal — single layout. responsive — horizontal at .
+ * vertical | horizontal — single layout. responsive — horizontal below md
+ * (640px), vertical from md up (Learn grid); one card, swapped via .
*/
variant?: ContentThumbnailTemplateVariantValue;
/**
@@ -28,6 +28,8 @@ export interface ContentThumbnailTemplateProps {
showLeadingImage?: boolean;
/** Alt for the tag mark. Empty when the mark repeats the title. */
leadingImageAlt?: string;
+ /** Tag mark source. Falls back to the catalog tag for the post slug. */
+ leadingImageSrc?: string;
}
export interface ContentThumbnailTemplateViewProps {
@@ -42,4 +44,5 @@ export interface ContentThumbnailTemplateViewProps {
showByline: boolean;
showLeadingImage: boolean;
leadingImageAlt?: string;
+ leadingImageSrc?: string;
}
diff --git a/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.view.tsx b/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.view.tsx
index 866574c..3eb15b3 100644
--- a/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.view.tsx
+++ b/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.view.tsx
@@ -14,18 +14,19 @@ function ContentThumbnailTemplateView({
showByline,
showLeadingImage,
leadingImageAlt,
+ leadingImageSrc,
}: ContentThumbnailTemplateViewProps) {
if (variant === "responsive") {
- // Single card; swaps the orientation-specific image at smd
- // (530px), aspect-ratio and content positioning switch via Tailwind.
+ // Single card; swaps to the vertical image at md (640px),
+ // which is where Learn's grid goes from one landscape card to two.
return (
-
+
{backgroundImageSmd ? (
) : null}
@@ -37,13 +38,14 @@ function ContentThumbnailTemplateView({
/>
-
@@ -72,6 +74,7 @@ function ContentThumbnailTemplateView({
showByline={showByline}
showLeadingImage={showLeadingImage}
leadingImageAlt={leadingImageAlt}
+ leadingImageSrc={leadingImageSrc}
/>
@@ -97,6 +100,7 @@ function ContentThumbnailTemplateView({
showByline={showByline}
showLeadingImage={showLeadingImage}
leadingImageAlt={leadingImageAlt}
+ leadingImageSrc={leadingImageSrc}
/>
@@ -124,6 +128,7 @@ function ContentThumbnailTemplateView({
showByline={showByline}
showLeadingImage={showLeadingImage}
leadingImageAlt={leadingImageAlt}
+ leadingImageSrc={leadingImageSrc}
/>
@@ -149,6 +154,7 @@ function ContentThumbnailTemplateView({
showByline={showByline}
showLeadingImage={showLeadingImage}
leadingImageAlt={leadingImageAlt}
+ leadingImageSrc={leadingImageSrc}
/>
diff --git a/public/content/blog/how-communityrule-works-horizontal.svg b/public/content/blog/how-communityrule-works-horizontal.svg
index d76a004..d841739 100644
--- a/public/content/blog/how-communityrule-works-horizontal.svg
+++ b/public/content/blog/how-communityrule-works-horizontal.svg
@@ -1,3 +1,32 @@
diff --git a/public/content/blog/how-communityrule-works-tag.svg b/public/content/blog/how-communityrule-works-tag.svg
new file mode 100644
index 0000000..b8fd584
--- /dev/null
+++ b/public/content/blog/how-communityrule-works-tag.svg
@@ -0,0 +1,14 @@
+
diff --git a/public/content/blog/how-communityrule-works-vertical.svg b/public/content/blog/how-communityrule-works-vertical.svg
index a3dc954..8d24983 100644
--- a/public/content/blog/how-communityrule-works-vertical.svg
+++ b/public/content/blog/how-communityrule-works-vertical.svg
@@ -1,3 +1,15 @@
diff --git a/tests/pages/learn.test.tsx b/tests/pages/learn.test.tsx
index 4e23ce8..7a5ad9c 100644
--- a/tests/pages/learn.test.tsx
+++ b/tests/pages/learn.test.tsx
@@ -119,7 +119,14 @@ describe("LearnPage", () => {
render();
const grid = screen.getByTestId("learn-article-grid");
- expect(grid).toHaveClass("smd:justify-center", "smd:flex-wrap");
+ expect(grid).toHaveClass("md:justify-center", "md:flex-wrap");
+ expect(grid).toHaveClass(
+ "lg:[&>*]:w-[calc((100%-2*var(--space-400))/3)]",
+ );
+ expect(grid).toHaveClass(
+ "xl:[&>*]:w-[calc((100%-3*var(--space-400))/4)]",
+ );
+ expect(grid.className).not.toContain("/5)]");
const links = within(grid).getAllByRole("link");
expect(links).toHaveLength(mockPosts.length + 1);
@@ -136,7 +143,7 @@ describe("LearnPage", () => {
expect(within(grid).queryByText("April 15, 2025")).not.toBeInTheDocument();
const sources = grid.querySelectorAll(
- "picture source[media='(min-width: 530px)']",
+ "picture source[media='(min-width: 640px)']",
);
expect(sources.length).toBe(mockPosts.length + 1);