From 6820315aaa195aac46198fef0ddb731ca915bed2 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Tue, 18 Aug 2026 22:11:02 -0600 Subject: [PATCH] Clip content-template ornaments at the frame so they do not overlap copy. Co-authored-by: Cursor --- .../ContentTemplateDecorativeShapes.tsx | 72 +++++++++++++++++++ app/(marketing)/blog/[slug]/page.tsx | 63 ++++------------ .../HowItWorksDecorativeShapes.tsx | 43 ----------- app/(marketing)/how-it-works/page.tsx | 8 +-- tests/pages/blog.test.jsx | 40 ++++++----- tests/pages/how-it-works.test.jsx | 29 ++++++++ 6 files changed, 143 insertions(+), 112 deletions(-) create mode 100644 app/(marketing)/_components/ContentTemplateDecorativeShapes.tsx delete mode 100644 app/(marketing)/how-it-works/_components/HowItWorksDecorativeShapes.tsx diff --git a/app/(marketing)/_components/ContentTemplateDecorativeShapes.tsx b/app/(marketing)/_components/ContentTemplateDecorativeShapes.tsx new file mode 100644 index 0000000..f0afa00 --- /dev/null +++ b/app/(marketing)/_components/ContentTemplateDecorativeShapes.tsx @@ -0,0 +1,72 @@ +/** + * Figma: Content page Template body ornaments (19003:23305). + * Hidden below md (640). From md, shapes sit in the side gutters and the + * page frame clips anything that bleeds past the viewport. + * + * - 19003:23575 / 19000:23008 — right + * - 19003:23576 / 19000:23009 — left + */ +import { + ASSETS, + getAssetPath, + howItWorksOrnamentLeftPath, + howItWorksOrnamentRightPath, +} from "../../../lib/assetUtils"; + +type ContentTemplateDecorativeShapesVariant = "guide" | "article"; + +type ContentTemplateDecorativeShapesProps = { + variant: ContentTemplateDecorativeShapesVariant; +}; + +const RIGHT_GUIDE_CLASS = + "pointer-events-none absolute z-0 hidden aspect-[155/250] md:block left-[87.19%] right-0 top-[266px] lg:left-[84.86%] lg:top-[255px]"; +const LEFT_GUIDE_CLASS = + "pointer-events-none absolute z-0 hidden aspect-[119/136] md:block left-0 right-[88.38%] top-[811px]"; +const RIGHT_ARTICLE_CLASS = + "pointer-events-none absolute z-0 hidden aspect-square md:block left-[87.19%] right-[-8.13%] top-[266px] lg:left-[84.86%] lg:right-[-9.28%] lg:top-[255px]"; +const LEFT_ARTICLE_CLASS = + "pointer-events-none absolute z-0 hidden aspect-square md:block left-[-1.66%] right-[88.38%] top-[811px]"; + +export default function ContentTemplateDecorativeShapes({ + variant, +}: ContentTemplateDecorativeShapesProps) { + const isArticle = variant === "article"; + + return ( + <> +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + +
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + +
+ + ); +} diff --git a/app/(marketing)/blog/[slug]/page.tsx b/app/(marketing)/blog/[slug]/page.tsx index e768339..513849a 100644 --- a/app/(marketing)/blog/[slug]/page.tsx +++ b/app/(marketing)/blog/[slug]/page.tsx @@ -10,7 +10,7 @@ import { import { logger } from "../../../../lib/logger"; import ContentBanner from "../../../components/sections/ContentBanner"; import AskOrganizer from "../../../components/sections/AskOrganizer"; -import { getAssetPath, ASSETS } from "../../../../lib/assetUtils"; +import ContentTemplateDecorativeShapes from "../../_components/ContentTemplateDecorativeShapes"; import "../blog.css"; // Code split RelatedArticles - blog-specific, below the fold @@ -205,58 +205,25 @@ export default async function BlogPostPage({ params }: PageProps) { className="relative min-h-screen overflow-x-clip" style={{ backgroundColor }} > - {/* Content Banner */} - {/* Decorative Shapes */} - {/* Right Side Shape (3/4 up the page) */} -
- {/* eslint-disable-next-line @next/next/no-img-element */} - -
+
+ - {/* Left Side Shape (3/4 down the page) */} -
- {/* eslint-disable-next-line @next/next/no-img-element */} - +
+
+
+
+
- {/* Main Content — Figma Content page Template (19003:23305) article body instances */} -
-
-
-
-
- {/* Related Articles Section */} -
- {/* eslint-disable-next-line @next/next/no-img-element */} - -
-
- {/* eslint-disable-next-line @next/next/no-img-element */} - -
- - ); -} diff --git a/app/(marketing)/how-it-works/page.tsx b/app/(marketing)/how-it-works/page.tsx index 9d2696c..c49b409 100644 --- a/app/(marketing)/how-it-works/page.tsx +++ b/app/(marketing)/how-it-works/page.tsx @@ -11,7 +11,7 @@ import { HOW_IT_WORKS_SENTINEL_SLUG, } from "../../../lib/howItWorksSyntheticPost"; import ContentBanner from "../../components/sections/ContentBanner"; -import HowItWorksDecorativeShapes from "./_components/HowItWorksDecorativeShapes"; +import ContentTemplateDecorativeShapes from "../_components/ContentTemplateDecorativeShapes"; import AskOrganizer from "../../components/sections/AskOrganizer"; import "../blog/blog.css"; @@ -103,11 +103,11 @@ export default function HowItWorksPage() { }} /> -
+
-
- +
+
{ }); // Mock asset utils -vi.mock("../../lib/assetUtils", () => ({ - getAssetPath: vi.fn((asset) => - asset.startsWith("/") ? asset : `/${asset}`, - ), - ASSETS: { - CONTENT_SHAPE_1: "assets/shapes/content-shape-1.svg", - CONTENT_SHAPE_2: "assets/shapes/content-shape-2.svg", - }, -})); +vi.mock("../../lib/assetUtils", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getAssetPath: vi.fn((asset) => + asset.startsWith("/") ? asset : `/${asset}`, + ), + }; +}); // Mock blog post data const mockPost = { @@ -238,6 +238,8 @@ describe("BlogPostPage", () => { // Check shape sources expect(shapes[0]).toHaveAttribute("src", "/assets/shapes/content-shape-1.svg"); expect(shapes[1]).toHaveAttribute("src", "/assets/shapes/content-shape-2.svg"); + expect(shapes[0]).toHaveClass("object-contain"); + expect(shapes[1]).toHaveClass("object-contain"); }); it("applies correct styling to article content", async () => { @@ -349,29 +351,33 @@ describe("BlogPostPage", () => { const shapes = screen.getAllByAltText(""); - // First shape (right side) const rightShape = shapes[0].closest("div"); expect(rightShape).toHaveClass( "hidden", "md:block", "absolute", - "top-1/4", - "right-0", "pointer-events-none", - "z-10", + "z-0", + "top-[266px]", + "lg:top-[255px]", + "left-[87.19%]", + "lg:left-[84.86%]", ); + expect(rightShape).not.toHaveClass("z-10"); + expect(rightShape).not.toHaveClass("top-1/4"); - // Second shape (left side) const leftShape = shapes[1].closest("div"); expect(leftShape).toHaveClass( "hidden", "md:block", "absolute", - "top-1/2", - "left-0", "pointer-events-none", - "z-10", + "z-0", + "top-[811px]", + "left-[-1.66%]", ); + expect(leftShape).not.toHaveClass("z-10"); + expect(leftShape).not.toHaveClass("top-1/2"); }); it("handles malformed post data gracefully", async () => { diff --git a/tests/pages/how-it-works.test.jsx b/tests/pages/how-it-works.test.jsx index 03cddf5..464aba6 100644 --- a/tests/pages/how-it-works.test.jsx +++ b/tests/pages/how-it-works.test.jsx @@ -63,4 +63,33 @@ describe("HowItWorksPage", () => { screen.getByText(/mutual aid network, manage an open-source project/), ).toBeInTheDocument(); }); + + test("clips body ornaments at the page frame and keeps them behind copy", () => { + const { container } = render(); + + const page = container.querySelector("div.min-h-screen"); + expect(page).toHaveClass("overflow-x-clip"); + + const right = container.querySelector('[data-node-id="19003:23575"]'); + const left = container.querySelector('[data-node-id="19003:23576"]'); + expect(right).toHaveClass( + "z-0", + "left-[87.19%]", + "right-0", + "top-[266px]", + "lg:left-[84.86%]", + "lg:top-[255px]", + ); + expect(left).toHaveClass("z-0", "left-0", "right-[88.38%]", "top-[811px]"); + + const rightImg = right?.querySelector("img"); + const leftImg = left?.querySelector("img"); + expect(rightImg).toHaveAttribute( + "src", + "/assets/shapes/how-shape-2.svg", + ); + expect(leftImg).toHaveAttribute("src", "/assets/shapes/how-shape-1.svg"); + expect(rightImg).toHaveClass("object-contain"); + expect(leftImg).toHaveClass("object-contain"); + }); });