From 40225e2845f19bbaf456a031aa8fd485177268a0 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Wed, 9 Sep 2026 17:40:11 -0600 Subject: [PATCH 1/3] Match the marketing homepage and About page to the latest Figma: black hero CTA, spaces in word-split headings, redesigned feature grid, and cited About statistics. Co-authored-by: Cursor --- app/(marketing)/about/page.tsx | 11 +--- app/components/cards/Stat/Stat.types.ts | 3 + app/components/cards/Stat/Stat.view.tsx | 23 ++++++- .../FeatureGrid/FeatureGrid.container.tsx | 58 +++++++---------- .../sections/FeatureGrid/FeatureGrid.types.ts | 11 ++-- .../sections/FeatureGrid/FeatureGrid.view.tsx | 55 +++++++++------- app/components/sections/Stats/Stats.types.ts | 4 ++ app/components/sections/Stats/Stats.view.tsx | 7 ++- .../type/AboutHeader/AboutHeader.view.tsx | 29 ++++++--- .../type/ContentLockup/ContentLockup.view.tsx | 15 +++-- .../type/SectionHeader/SectionHeader.tsx | 4 +- lib/assetUtils.ts | 30 +++------ messages/en/components/featureGrid.json | 12 ++-- messages/en/pages/about.json | 26 +++++--- messages/en/pages/home.json | 20 +++--- .../feature-icon-arrow-left-right.svg | 5 ++ .../marketing/feature-icon-door-open.svg | 5 ++ .../marketing/feature-icon-git-branch.svg | 5 ++ .../feature-icon-message-square-share.svg | 5 ++ stories/sections/FeatureGrid.stories.js | 12 ++-- stories/type/ContentLockup.stories.js | 6 +- tests/components/FeatureGrid.test.tsx | 62 +++++++------------ tests/components/HeroBanner.test.tsx | 9 +-- tests/components/SectionHeader.test.tsx | 17 +++++ tests/components/cards/Stat.test.tsx | 20 ++++++ tests/components/type/AboutHeader.test.tsx | 21 +++++++ tests/components/type/ContentLockup.test.tsx | 18 ++++++ tests/e2e/critical-journeys.spec.ts | 33 +++++----- tests/pages/about.test.tsx | 23 +++++++ tests/pages/home.test.jsx | 20 +++--- tests/pages/page-flow.test.jsx | 6 +- tests/pages/user-journey.test.jsx | 6 +- 32 files changed, 362 insertions(+), 219 deletions(-) create mode 100644 public/assets/marketing/feature-icon-arrow-left-right.svg create mode 100644 public/assets/marketing/feature-icon-door-open.svg create mode 100644 public/assets/marketing/feature-icon-git-branch.svg create mode 100644 public/assets/marketing/feature-icon-message-square-share.svg diff --git a/app/(marketing)/about/page.tsx b/app/(marketing)/about/page.tsx index db50a7c..8ce856f 100644 --- a/app/(marketing)/about/page.tsx +++ b/app/(marketing)/about/page.tsx @@ -36,11 +36,6 @@ export default function AboutPage() { const headerSegments = asArray(page.aboutHeader.segments); const statsItems = asArray(page.stats.items); - const statsAsOf = - typeof page.stats.asOf === "string" - ? page.stats.asOf - : String(page.stats.asOf ?? ""); - const faqItems = asArray(page.faq.items); const tripleColumns = asArray(page.tripleTextBlock.columns); @@ -57,10 +52,8 @@ export default function AboutPage() { titlePrefix={page.stats.titlePrefix} titleEmphasis={page.stats.titleEmphasis} titleSuffix={page.stats.titleSuffix} - items={statsItems.map((item) => ({ - ...item, - asOf: statsAsOf, - }))} + asOfPrefix={page.stats.asOfPrefix} + items={statsItems} /> {asOf ? (

- {asOf} + {sourcedYear ? ( + <> + {asOfPrefix ? {`${asOfPrefix} `} : null} + {sourceHref ? ( + + {asOf} + {sourceName ? ( + {` (${sourceName})`} + ) : null} + + ) : ( + {asOf} + )} + + ) : ( + asOf + )}

) : null} diff --git a/app/components/sections/FeatureGrid/FeatureGrid.container.tsx b/app/components/sections/FeatureGrid/FeatureGrid.container.tsx index a993dfb..430ffee 100644 --- a/app/components/sections/FeatureGrid/FeatureGrid.container.tsx +++ b/app/components/sections/FeatureGrid/FeatureGrid.container.tsx @@ -1,11 +1,11 @@ "use client"; /** - * Figma: "Section / Feature-Grid" (18847:22410) + * Figma: "Section / Feature-Grid" (18632:10668) */ import { memo, useMemo } from "react"; -import { getAssetPath, featurePanelLayout, featurePanelPath } from "../../../../lib/assetUtils"; +import { featureIconPath, getAssetPath } from "../../../../lib/assetUtils"; import { useTranslation } from "../../../contexts/MessagesContext"; import FeatureGridView from "./FeatureGrid.view"; import type { FeatureGridProps, Feature } from "./FeatureGrid.types"; @@ -17,52 +17,40 @@ const FeatureGridContainer = memo( const features: Feature[] = useMemo( () => [ { - backgroundColor: "bg-[var(--color-surface-invert-brand-royal)]", - labelLine1: t( - "pages.home.featureGrid.features.decisionMaking.labelLine1", + backgroundColor: "bg-[var(--color-surface-invert-brand-lavender)]", + title: t("pages.home.featureGrid.features.decisionMaking.title"), + description: t( + "pages.home.featureGrid.features.decisionMaking.description", ), - labelLine2: t( - "pages.home.featureGrid.features.decisionMaking.labelLine2", - ), - panelContent: getAssetPath(featurePanelPath("support")), - ...featurePanelLayout("support"), - ariaLabel: t("featureGrid.features.decisionMaking.ariaLabel"), + descriptionClassName: "text-[#3a3273]", + iconSrc: getAssetPath(featureIconPath("git-branch")), }, { backgroundColor: "bg-[var(--color-surface-invert-brand-lime)]", - labelLine1: t( - "pages.home.featureGrid.features.valuesAlignment.labelLine1", + title: t("pages.home.featureGrid.features.valuesAlignment.title"), + description: t( + "pages.home.featureGrid.features.valuesAlignment.description", ), - labelLine2: t( - "pages.home.featureGrid.features.valuesAlignment.labelLine2", - ), - panelContent: getAssetPath(featurePanelPath("exercises")), - ...featurePanelLayout("exercises"), - ariaLabel: t("featureGrid.features.valuesAlignment.ariaLabel"), + descriptionClassName: "text-[#424f1a]", + iconSrc: getAssetPath(featureIconPath("arrow-left-right")), }, { backgroundColor: "bg-[var(--color-surface-invert-brand-rust)]", - labelLine1: t( - "pages.home.featureGrid.features.membershipGuidance.labelLine1", + title: t("pages.home.featureGrid.features.membershipGuidance.title"), + description: t( + "pages.home.featureGrid.features.membershipGuidance.description", ), - labelLine2: t( - "pages.home.featureGrid.features.membershipGuidance.labelLine2", - ), - panelContent: getAssetPath(featurePanelPath("guidance")), - ...featurePanelLayout("guidance"), - ariaLabel: t("featureGrid.features.membershipGuidance.ariaLabel"), + descriptionClassName: "text-[#5e2e23]", + iconSrc: getAssetPath(featureIconPath("door-open")), }, { backgroundColor: "bg-[var(--color-surface-invert-brand-teal)]", - labelLine1: t( - "pages.home.featureGrid.features.conflictResolution.labelLine1", + title: t("pages.home.featureGrid.features.conflictResolution.title"), + description: t( + "pages.home.featureGrid.features.conflictResolution.description", ), - labelLine2: t( - "pages.home.featureGrid.features.conflictResolution.labelLine2", - ), - panelContent: getAssetPath(featurePanelPath("tools")), - ...featurePanelLayout("tools"), - ariaLabel: t("featureGrid.features.conflictResolution.ariaLabel"), + descriptionClassName: "text-[#1f4d47]", + iconSrc: getAssetPath(featureIconPath("message-square-share")), }, ], [t], diff --git a/app/components/sections/FeatureGrid/FeatureGrid.types.ts b/app/components/sections/FeatureGrid/FeatureGrid.types.ts index b49af6c..cbb0f58 100644 --- a/app/components/sections/FeatureGrid/FeatureGrid.types.ts +++ b/app/components/sections/FeatureGrid/FeatureGrid.types.ts @@ -6,13 +6,10 @@ export interface FeatureGridProps { export interface Feature { backgroundColor: string; - labelLine1: string; - labelLine2: string; - panelContent: string; - panelWidth: number; - panelHeight: number; - panelImageClassName?: string; - ariaLabel: string; + title: string; + description: string; + descriptionClassName: string; + iconSrc: string; } export interface FeatureGridViewProps extends FeatureGridProps { diff --git a/app/components/sections/FeatureGrid/FeatureGrid.view.tsx b/app/components/sections/FeatureGrid/FeatureGrid.view.tsx index d639a74..5425f07 100644 --- a/app/components/sections/FeatureGrid/FeatureGrid.view.tsx +++ b/app/components/sections/FeatureGrid/FeatureGrid.view.tsx @@ -2,10 +2,9 @@ import { useTranslation } from "../../../contexts/MessagesContext"; import ContentLockup from "../../type/ContentLockup"; -import Mini from "../../cards/Mini"; import type { FeatureGridViewProps } from "./FeatureGrid.types"; -/** Figma **Section / Feature-Grid** [18847:22410](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=18847-22410&m=dev). */ +/** Figma **Section / Feature-Grid** [18632:10668](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=18632-10668&m=dev). */ function FeatureGridView({ title, subtitle, @@ -20,16 +19,16 @@ function FeatureGridView({ return (
-
-
+
+
-
- {features.map((feature, index) => ( - +
+ {features.map((feature) => ( +
+
+ {/* eslint-disable-next-line @next/next/no-img-element -- decorative Figma glyph */} + +
+
+

+ {feature.title} +

+

+ {feature.description} +

+
+
))}
diff --git a/app/components/sections/Stats/Stats.types.ts b/app/components/sections/Stats/Stats.types.ts index da700e4..741bf56 100644 --- a/app/components/sections/Stats/Stats.types.ts +++ b/app/components/sections/Stats/Stats.types.ts @@ -4,6 +4,9 @@ export interface StatItem { value: string; label: string; asOf?: string; + asOfPrefix?: string; + sourceHref?: string; + sourceName?: string; shapeVariant?: StatShapeVariant; } @@ -11,6 +14,7 @@ export interface StatsProps { titlePrefix?: string; titleEmphasis?: string; titleSuffix?: string; + asOfPrefix?: string; items: StatItem[]; className?: string; } diff --git a/app/components/sections/Stats/Stats.view.tsx b/app/components/sections/Stats/Stats.view.tsx index c4499eb..41560fd 100644 --- a/app/components/sections/Stats/Stats.view.tsx +++ b/app/components/sections/Stats/Stats.view.tsx @@ -21,6 +21,7 @@ function StatsView({ titlePrefix, titleEmphasis, titleSuffix, + asOfPrefix, items, headingId, className = "", @@ -88,7 +89,11 @@ function StatsView({ return (
  • - +
  • ); })} diff --git a/app/components/type/AboutHeader/AboutHeader.view.tsx b/app/components/type/AboutHeader/AboutHeader.view.tsx index a9d148a..f1d148d 100644 --- a/app/components/type/AboutHeader/AboutHeader.view.tsx +++ b/app/components/type/AboutHeader/AboutHeader.view.tsx @@ -25,16 +25,27 @@ function AboutHeaderView({ variant="about" alignment="left" titleId={titleId} - titleContent={segments.map((segment, index) => { - if (segment.type === "word") { - return ( - - {segment.text} + titleContent={segments.flatMap((segment, index) => { + const spaceBefore = + segment.type === "word" && index > 0 ? ( + + {" "} - ); + ) : null; + + if (segment.type === "word") { + return [ + spaceBefore, + + {segment.text} + , + ]; } - return ( + return [ - - ); + , + ]; })} />
    diff --git a/app/components/type/ContentLockup/ContentLockup.view.tsx b/app/components/type/ContentLockup/ContentLockup.view.tsx index 224e6d8..0707a97 100644 --- a/app/components/type/ContentLockup/ContentLockup.view.tsx +++ b/app/components/type/ContentLockup/ContentLockup.view.tsx @@ -84,8 +84,13 @@ function ContentLockupView({ )} - {/* Subtitle */} - {subtitle ?

    {subtitle}

    : null} + {subtitle ? ( + variant === "feature" ? ( +

    {subtitle}

    + ) : ( +

    {subtitle}

    + ) + ) : null} {/* Description */} @@ -110,7 +115,7 @@ function ContentLockupView({
    )} - {/* Link for feature variant */} {variant === "feature" && linkText && linkHref && ( - {linkText} - + )} {/* CTA Button */} diff --git a/docs/guides/static-assets.md b/docs/guides/static-assets.md index 22c2c19..46313e1 100644 --- a/docs/guides/static-assets.md +++ b/docs/guides/static-assets.md @@ -8,7 +8,7 @@ Convention for files served from `public/`. Path helpers live in ``` public/ assets/ - icons/ # UI chrome (alert, close, help, pointer) + icons/ # UI chrome (close, help) + FeatureGrid card glyphs logos/ # Brand + social lockups partners/ # Logo wall partner SVGs (kebab org slug) marketing/ # Hero, feature panels, section numbers, avatars, banners, book cover @@ -25,7 +25,7 @@ public/ | Location | Used for | Resolution | | --- | --- | --- | -| `public/assets/icons/` | Static chrome served by URL (`icon-close.svg`, `icon-help.svg`) | `ASSETS.ICON_*` | +| `public/assets/icons/` | Static chrome served by URL (`icon-close.svg`, `icon-help.svg`) and FeatureGrid card glyphs (`icon-git-branch.svg`, …) | `ASSETS.ICON_*`, `featureIconPath()` | | `app/components/asset/icon/` | Bundled create-flow / nav SVGs imported by `Icon.tsx` | Webpack import, not `public/` | Do not duplicate the same glyph in both places unless migrating between systems. @@ -61,7 +61,8 @@ stage. Raster → SVG conversion is tracked in | Path | Used by | Disposition | | --- | --- | --- | | `logos/partners/*.svg` (×6) | LogoWall | **Done** — SVG (kebab org slug, no `logo-` prefix) | -| `marketing/feature-*.svg` (×4) | FeatureGrid | Exported from Figma Section/Feature-Grid (18847:22410) | +| `marketing/feature-*.svg` (×4) | Mini cards | Panel art (`support`, `exercises`, `guidance`, `tools`) | +| `icons/icon-{git-branch,arrow-left-right,door-open,message-square-share}.svg` | FeatureGrid | Card glyphs from Figma Section/Feature-Grid (18632:10668) | | `marketing/section-number-*.svg` (×3) | SectionNumber | **Done** — SVG | | `marketing/avatar-*.svg` (×3) | Avatar / ASSETS | **Done** — SVG | | `marketing/hero-image.png` | HeroBanner | **Design review** — likely keep raster | diff --git a/lib/assetUtils.ts b/lib/assetUtils.ts index 3661369..1be1024 100644 --- a/lib/assetUtils.ts +++ b/lib/assetUtils.ts @@ -114,7 +114,7 @@ export function featurePanelPath(key: FeaturePanelKey): string { return `assets/marketing/feature-${key}.svg`; } -/** Feature-grid card glyphs (Figma **18632:10668**). */ +/** Feature-grid card glyphs in `public/assets/icons/` (Figma **18632:10668**). */ export type FeatureIconKey = | "git-branch" | "arrow-left-right" @@ -122,7 +122,7 @@ export type FeatureIconKey = | "message-square-share"; export function featureIconPath(key: FeatureIconKey): string { - return `assets/marketing/feature-icon-${key}.svg`; + return `assets/icons/icon-${key}.svg`; } /** Case study card artwork in `public/assets/case-study/`. */ diff --git a/public/assets/marketing/feature-icon-arrow-left-right.svg b/public/assets/icons/icon-arrow-left-right.svg similarity index 100% rename from public/assets/marketing/feature-icon-arrow-left-right.svg rename to public/assets/icons/icon-arrow-left-right.svg diff --git a/public/assets/marketing/feature-icon-door-open.svg b/public/assets/icons/icon-door-open.svg similarity index 100% rename from public/assets/marketing/feature-icon-door-open.svg rename to public/assets/icons/icon-door-open.svg diff --git a/public/assets/marketing/feature-icon-git-branch.svg b/public/assets/icons/icon-git-branch.svg similarity index 100% rename from public/assets/marketing/feature-icon-git-branch.svg rename to public/assets/icons/icon-git-branch.svg diff --git a/public/assets/marketing/feature-icon-message-square-share.svg b/public/assets/icons/icon-message-square-share.svg similarity index 100% rename from public/assets/marketing/feature-icon-message-square-share.svg rename to public/assets/icons/icon-message-square-share.svg diff --git a/tests/components/FeatureGrid.test.tsx b/tests/components/FeatureGrid.test.tsx index 6400edc..c5570a8 100644 --- a/tests/components/FeatureGrid.test.tsx +++ b/tests/components/FeatureGrid.test.tsx @@ -132,6 +132,7 @@ describe("FeatureGrid (behavioral tests)", () => { expect(icon).toHaveAttribute("width", "40"); expect(icon).toHaveAttribute("height", "40"); expect(icon).toHaveAttribute("alt", ""); + expect(icon.getAttribute("src")).toMatch(/^\/assets\/icons\/icon-/); }); }); }); diff --git a/tests/components/type/ContentLockup.test.tsx b/tests/components/type/ContentLockup.test.tsx index d5f0b7b..d99ecb9 100644 --- a/tests/components/type/ContentLockup.test.tsx +++ b/tests/components/type/ContentLockup.test.tsx @@ -63,4 +63,24 @@ describe("ContentLockup", () => { expect(description.className).toContain("text-small-paragraph"); expect(description.className).toContain("sm:text-large-paragraph"); }); + + it("renders the feature lockup CTA as an inline text link", () => { + renderWithProviders( + , + ); + + const link = screen.getByRole("link", { name: "Explore the toolkit" }); + expect(link).toHaveAttribute("href", "/learn"); + expect(link.className).toMatch(/underline/); + expect(link.className).toMatch(/w-fit/); + expect(link.className).not.toMatch(/px-1/); + expect(link.className).not.toMatch(/focus:ring-2/); + expect(link.className).toMatch(/focus-visible:outline/); + }); }); From 4bd9fa6dc658a90e1808d5b58e5e6976612d744a Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Wed, 9 Sep 2026 21:39:39 -0600 Subject: [PATCH 3/3] Restore the folder header to Figma layout without cloning the nav, and page-center inner-page links with the home cluster. Park the skip link in the Tailwind sheet so it cannot sit in document flow and push the yellow tab down. Co-authored-by: Cursor --- app/components/asset/Logo/Logo.tsx | 2 +- .../navigation/Top/Top.container.tsx | 31 +++++++++++++---- app/components/navigation/Top/Top.view.tsx | 31 +++++++++-------- app/globals.css | 23 ------------- app/tailwind.css | 27 +++++++++++++++ tests/components/Logo.test.tsx | 6 ++++ tests/components/Top.test.tsx | 33 +++++++++++++++++++ 7 files changed, 110 insertions(+), 43 deletions(-) diff --git a/app/components/asset/Logo/Logo.tsx b/app/components/asset/Logo/Logo.tsx index 37ddd26..9f0313e 100644 --- a/app/components/asset/Logo/Logo.tsx +++ b/app/components/asset/Logo/Logo.tsx @@ -93,7 +93,7 @@ const Logo = memo( ? "text-[var(--color-content-invert-primary)]" : "text-[var(--color-content-default-primary)]"; const wordmarkVisibilityClass = - size === "topNavFolderTop" || size === "topNavHeader" + size === "topNavHeader" ? wordmark ? "hidden md:block" : "hidden" diff --git a/app/components/navigation/Top/Top.container.tsx b/app/components/navigation/Top/Top.container.tsx index b9aa8f3..abf8eb8 100644 --- a/app/components/navigation/Top/Top.container.tsx +++ b/app/components/navigation/Top/Top.container.tsx @@ -1,7 +1,8 @@ "use client"; /** - * Figma: "Navigation / Top" (22078-808559) + * Figma: "Navigation / Top" (22078-808559); Folder-top 430–639 (18582:22056), + * 640–1023 (18580:17113). */ import { memo, useCallback } from "react"; @@ -39,6 +40,9 @@ const FOLDER_NAV_ITEM_RESPONSIVE_CLASS = const CREATE_RULE_RESPONSIVE_CLASS = "lg:p-[var(--spacing-scale-012)] lg:gap-[var(--spacing-scale-006)] lg:text-medium-label xl:p-[var(--spacing-scale-016)] xl:gap-[var(--spacing-scale-008)] xl:text-x-large-label"; +const FOLDER_CREATE_RULE_RESPONSIVE_CLASS = + "md:p-[var(--spacing-scale-008)] md:gap-[var(--spacing-scale-002)] md:text-x-small-label lg:p-[var(--spacing-scale-012)] lg:gap-[var(--spacing-scale-006)] lg:text-medium-label xl:p-[var(--spacing-scale-016)] xl:gap-[var(--spacing-scale-008)] xl:text-x-large-label"; + const TopContainer = memo( ({ folderTop = false, loggedIn = false, profile = false, logIn = true }) => { const pathname = usePathname(); @@ -120,6 +124,9 @@ const TopContainer = memo( const navSelected = (loggedIn && pathname === "/profile") || (!loggedIn && pathname === "/login"); + const loginClassName = folderTop + ? FOLDER_NAV_ITEM_RESPONSIVE_CLASS + : NAV_ITEM_RESPONSIVE_CLASS; if (loggedIn) { return ( @@ -128,7 +135,7 @@ const TopContainer = memo( size="X Small" mode="default" state={navSelected ? "selected" : "default"} - className={NAV_ITEM_RESPONSIVE_CLASS} + className={loginClassName} ariaLabel={ariaLabel} > {label} @@ -149,7 +156,7 @@ const TopContainer = memo( size="X Small" mode="default" state={navSelected ? "selected" : "default"} - className={NAV_ITEM_RESPONSIVE_CLASS} + className={loginClassName} ariaLabel={ariaLabel} > {label} @@ -165,11 +172,19 @@ const TopContainer = memo( palette="inverse" onClick={handleCreateRuleClick} ariaLabel={t("ariaLabels.createNewRule")} - className={CREATE_RULE_RESPONSIVE_CLASS} + className={ + folderTop + ? FOLDER_CREATE_RULE_RESPONSIVE_CLASS + : CREATE_RULE_RESPONSIVE_CLASS + } > {avatarImageSources.map((src, index) => ( ( src={src} alt={tTopNav(`avatarAlts.${3 - index}`)} size="small" - className="lg:h-[var(--spacing-scale-024)] lg:w-[var(--spacing-scale-024)] xl:h-[var(--spacing-scale-032)] xl:w-[var(--spacing-scale-032)]" + className={ + folderTop + ? "md:h-[var(--spacing-scale-018)] md:w-[var(--spacing-scale-018)] lg:h-[var(--spacing-scale-024)] lg:w-[var(--spacing-scale-024)] xl:h-[var(--spacing-scale-032)] xl:w-[var(--spacing-scale-032)]" + : "lg:h-[var(--spacing-scale-024)] lg:w-[var(--spacing-scale-024)] xl:h-[var(--spacing-scale-032)] xl:w-[var(--spacing-scale-032)]" + } /> ))} diff --git a/app/components/navigation/Top/Top.view.tsx b/app/components/navigation/Top/Top.view.tsx index ee07e2f..9a6f9f8 100644 --- a/app/components/navigation/Top/Top.view.tsx +++ b/app/components/navigation/Top/Top.view.tsx @@ -37,17 +37,17 @@ function TopView({ dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }} />