From 33cdaf135c9b2b7fe3e28a17c92b0c67346ed80b Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Mon, 2 Nov 2026 18:50:32 -0700 Subject: [PATCH 1/3] Match Home, top nav, hero, and logo wall to the current marketing frames. Puts Workshops in the header, pairs the hero with a static rule card and breakpoint CTAs, and shows the three pilot marks with no wall label. Co-authored-by: Cursor --- app/(marketing)/page.tsx | 3 - app/components/asset/Logo/Logo.tsx | 12 +- app/components/cards/Rule/Rule.container.tsx | 15 ++- app/components/cards/Rule/Rule.types.ts | 6 + app/components/cards/Rule/Rule.view.tsx | 3 +- .../navigation/Top/Top.container.tsx | 66 ++++----- app/components/navigation/Top/Top.types.ts | 2 +- app/components/navigation/Top/Top.view.tsx | 35 +++-- .../AskOrganizer/AskOrganizer.view.tsx | 1 + .../sections/FeatureGrid/FeatureGrid.view.tsx | 15 ++- .../HeroBanner/HeroBanner.container.tsx | 85 ++++++++++++ .../sections/HeroBanner/HeroBanner.tsx | 83 ------------ .../sections/HeroBanner/HeroBanner.types.ts | 20 +++ .../sections/HeroBanner/HeroBanner.view.tsx | 125 ++++++++++++++++++ app/components/sections/HeroBanner/index.tsx | 3 +- .../sections/LogoWall/LogoWall.container.tsx | 42 ++---- .../sections/LogoWall/LogoWall.types.ts | 3 - .../sections/LogoWall/LogoWall.view.tsx | 52 +++----- .../ContentLockup/ContentLockup.container.tsx | 2 + .../type/ContentLockup/ContentLockup.types.ts | 9 ++ .../type/ContentLockup/ContentLockup.view.tsx | 15 ++- docs/figma-component-registry.md | 2 +- messages/en/components/featureGrid.json | 4 +- messages/en/components/header.json | 2 +- messages/en/components/heroBanner.json | 48 ++++++- messages/en/components/logoWall.json | 9 +- messages/en/navigation.json | 2 +- messages/en/pages/home.json | 36 +++-- .../logos/partners/boulder-street-medics.svg | 56 ++++++++ stories/sections/CardSteps.stories.js | 10 +- stories/sections/FeatureGrid.stories.js | 2 +- stories/sections/HeroBanner.stories.js | 20 +-- stories/sections/LogoWall.stories.js | 46 +------ tests/components/AskOrganizer.test.tsx | 6 +- tests/components/FeatureGrid.test.tsx | 10 +- tests/components/HeroBanner.test.tsx | 23 +++- tests/components/Logo.test.tsx | 12 +- tests/components/Top.test.tsx | 28 ++-- tests/components/TopWithPathname.test.tsx | 4 +- tests/components/type/ContentLockup.test.tsx | 23 ++++ tests/e2e/critical-journeys.spec.ts | 42 +++--- tests/e2e/edge-cases.spec.ts | 65 +++------ tests/pages/home.test.jsx | 60 ++++----- tests/pages/page-flow.test.jsx | 56 ++++---- tests/pages/user-journey.test.jsx | 33 +++-- tests/unit/LogoWall.test.jsx | 55 +++----- tests/unit/RuleStack.test.jsx | 4 +- 47 files changed, 728 insertions(+), 527 deletions(-) create mode 100644 app/components/sections/HeroBanner/HeroBanner.container.tsx delete mode 100644 app/components/sections/HeroBanner/HeroBanner.tsx create mode 100644 app/components/sections/HeroBanner/HeroBanner.types.ts create mode 100644 app/components/sections/HeroBanner/HeroBanner.view.tsx create mode 100644 public/assets/logos/partners/boulder-street-medics.svg diff --git a/app/(marketing)/page.tsx b/app/(marketing)/page.tsx index 8c30ccf..ad82ead 100644 --- a/app/(marketing)/page.tsx +++ b/app/(marketing)/page.tsx @@ -51,9 +51,6 @@ export default function Page() { title: t("pages.home.heroBanner.title"), subtitle: t("pages.home.heroBanner.subtitle"), description: t("pages.home.heroBanner.description"), - ctaText: t("pages.home.heroBanner.ctaText"), - ctaHref: t("pages.home.heroBanner.ctaHref"), - imageAlt: t("heroBanner.imageAlt"), }; const cardStepsData = { diff --git a/app/components/asset/Logo/Logo.tsx b/app/components/asset/Logo/Logo.tsx index 3b69457..996362d 100644 --- a/app/components/asset/Logo/Logo.tsx +++ b/app/components/asset/Logo/Logo.tsx @@ -95,11 +95,15 @@ const Logo = memo( const wordmarkVisibilityClass = size === "topNavHeader" ? wordmark - ? "hidden md:block" + ? "hidden sm:block" : "hidden" - : wordmark - ? "" - : "hidden"; + : size === "topNavFolderTop" + ? wordmark + ? "hidden md:block" + : "hidden" + : wordmark + ? "" + : "hidden"; return ( diff --git a/app/components/cards/Rule/Rule.container.tsx b/app/components/cards/Rule/Rule.container.tsx index 70768b9..88f4fe4 100644 --- a/app/components/cards/Rule/Rule.container.tsx +++ b/app/components/cards/Rule/Rule.container.tsx @@ -43,6 +43,7 @@ const RuleContainer = memo( logoAlt, communityInitials, hideCategoryAddButton = false, + interactive, hasBottomLinks = false, bottomStatusLabel, bottomLinks, @@ -53,12 +54,13 @@ const RuleContainer = memo( surfaceColor, }) => { const size = sizeProp ?? "L"; + const isInteractive = interactive ?? !hasBottomLinks; const t = useTranslation("ruleCard"); const cardAriaLabel = t("ariaLabel")?.replace("{title}", title) || title; const recommendedLabel = t("recommendedLabel"); const handleClick = () => { - if (hasBottomLinks) return; + if (!isInteractive || hasBottomLinks) return; // Basic analytics event tracking if (typeof window !== "undefined" && window.gtag) { window.gtag("event", "template_selected", { @@ -79,7 +81,7 @@ const RuleContainer = memo( }; const handleKeyDown = (event: React.KeyboardEvent) => { - if (hasBottomLinks) return; + if (!isInteractive || hasBottomLinks) return; if (event.key === "Enter" || event.key === " ") { event.preventDefault(); handleClick(); @@ -98,9 +100,11 @@ const RuleContainer = memo( icon={icon} backgroundColor={backgroundColor} className={className} - href={hasBottomLinks ? undefined : href} - onClick={hasBottomLinks ? undefined : handleClick} - onKeyDown={hasBottomLinks || href ? undefined : handleKeyDown} + href={isInteractive && !hasBottomLinks ? href : undefined} + onClick={isInteractive && !hasBottomLinks ? handleClick : undefined} + onKeyDown={ + isInteractive && !hasBottomLinks && !href ? handleKeyDown : undefined + } expanded={expanded} size={size} categories={categories} @@ -108,6 +112,7 @@ const RuleContainer = memo( logoAlt={logoAlt} communityInitials={communityInitials} hideCategoryAddButton={hideCategoryAddButton} + interactive={isInteractive} hasBottomLinks={hasBottomLinks} bottomStatusLabel={bottomStatusLabel} bottomLinks={bottomLinks} diff --git a/app/components/cards/Rule/Rule.types.ts b/app/components/cards/Rule/Rule.types.ts index d4b8303..1bca614 100644 --- a/app/components/cards/Rule/Rule.types.ts +++ b/app/components/cards/Rule/Rule.types.ts @@ -63,6 +63,11 @@ export interface RuleProps { communityInitials?: string; /** Hide the per-category "+" add chip affordance (e.g. read-only template review). */ hideCategoryAddButton?: boolean; + /** + * When false, the card is a static article (Home hero). Default follows + * {@link hasBottomLinks}: interactive unless bottom links are shown. + */ + interactive?: boolean; /** * Figma `Card / Rule` variant: description + optional status chip + text links * (e.g. Duplicate / Delete, or Continue / Start new rule). When set, the card @@ -117,6 +122,7 @@ export interface RuleViewProps { logoAlt?: string; communityInitials?: string; hideCategoryAddButton?: boolean; + interactive?: boolean; hasBottomLinks?: boolean; bottomStatusLabel?: string; bottomLinks?: RuleBottomLink[]; diff --git a/app/components/cards/Rule/Rule.view.tsx b/app/components/cards/Rule/Rule.view.tsx index 4465e1c..6700353 100644 --- a/app/components/cards/Rule/Rule.view.tsx +++ b/app/components/cards/Rule/Rule.view.tsx @@ -29,6 +29,7 @@ export function RuleView({ logoAlt, communityInitials, hideCategoryAddButton = false, + interactive, hasBottomLinks = false, bottomStatusLabel, bottomLinks, @@ -41,7 +42,7 @@ export function RuleView({ recommendedLabel, }: RuleViewProps) { const ariaLabel = cardAriaLabel; - const interactiveCard = !hasBottomLinks; + const interactiveCard = interactive ?? !hasBottomLinks; // Size-based styling const isLarge = size === "L"; diff --git a/app/components/navigation/Top/Top.container.tsx b/app/components/navigation/Top/Top.container.tsx index ae9b420..64f3964 100644 --- a/app/components/navigation/Top/Top.container.tsx +++ b/app/components/navigation/Top/Top.container.tsx @@ -1,8 +1,8 @@ "use client"; /** - * Figma: "Navigation / Top" (22078-808559); Folder-top 430–639 (18582:22056), - * 640–1023 (18580:17113). + * Figma: "Navigation / Top" (5944:17779); Folder-top 320–429 (18583:22098), + * 430–639 (18582:22056); standard 320–429 (18411:57843). */ import { memo, useCallback } from "react"; @@ -82,7 +82,7 @@ const TopContainer = memo( const logoSize = folderTop ? "topNavFolderTop" : "topNavHeader"; const navigationItems = [ - { href: "/use-cases", text: t("navigation.useCases"), extraPadding: true }, + { href: "/workshops", text: t("navigation.workshops") }, { href: "/learn", text: t("navigation.learn") }, { href: "/about", text: t("navigation.about") }, ]; @@ -93,30 +93,25 @@ const TopContainer = memo( ? FOLDER_NAV_ITEM_RESPONSIVE_CLASS : NAV_ITEM_RESPONSIVE_CLASS; - return navigationItems.map((item) => { - const isUseCases = item.extraPadding === true; - - return ( - - {item.text} - - ); - }); + return navigationItems.map((item) => ( + + {item.text} + + )); }; - const renderLoginButton = () => { + const renderLoginButton = (className = "") => { const label = loggedIn ? t("buttons.profile") : t("buttons.logIn"); const ariaLabel = loggedIn ? t("ariaLabels.goToProfile") @@ -124,16 +119,23 @@ const TopContainer = memo( const navSelected = (loggedIn && pathname === "/profile") || (!loggedIn && pathname === "/login"); - const loginClassName = folderTop - ? FOLDER_NAV_ITEM_RESPONSIVE_CLASS - : NAV_ITEM_RESPONSIVE_CLASS; + const loginClassName = [ + folderTop + ? FOLDER_NAV_ITEM_RESPONSIVE_CLASS + : NAV_ITEM_RESPONSIVE_CLASS, + className, + ] + .filter(Boolean) + .join(" "); + const inFolderTab = folderTop && className.includes("md:hidden"); + const mode = inFolderTab ? "inverse" : "default"; if (loggedIn) { return ( ( } href="/login" size="X Small" - mode="default" + mode={mode} state={navSelected ? "selected" : "default"} className={loginClassName} ariaLabel={ariaLabel} @@ -182,8 +184,8 @@ const TopContainer = memo( size="small" className={ folderTop - ? "md:-space-x-[9px] lg:-space-x-[var(--space-250)] xl:-space-x-[13px]" - : "lg:-space-x-[var(--space-250)] xl:-space-x-[13px]" + ? "max-sm:hidden md:-space-x-[9px] lg:-space-x-[var(--space-250)] xl:-space-x-[13px]" + : "max-sm:hidden lg:-space-x-[var(--space-250)] xl:-space-x-[13px]" } > {avatarImageSources.map((src, index) => ( diff --git a/app/components/navigation/Top/Top.types.ts b/app/components/navigation/Top/Top.types.ts index f22a8a6..99f8028 100644 --- a/app/components/navigation/Top/Top.types.ts +++ b/app/components/navigation/Top/Top.types.ts @@ -28,6 +28,6 @@ export interface TopViewProps { }; logoSize: "topNavFolderTop" | "topNavHeader"; renderNavigationItems: () => React.ReactNode; - renderLoginButton: () => React.ReactNode; + renderLoginButton: (_className?: string) => React.ReactNode; renderCreateRuleButton: () => React.ReactNode; } diff --git a/app/components/navigation/Top/Top.view.tsx b/app/components/navigation/Top/Top.view.tsx index 968900b..eb845db 100644 --- a/app/components/navigation/Top/Top.view.tsx +++ b/app/components/navigation/Top/Top.view.tsx @@ -22,8 +22,9 @@ function TopView({ }: TopViewProps) { const t = useTranslation(folderTop ? "homeHeader" : "header"); - const loginControl = logIn ? ( - + const phoneAuthItem = logIn ? renderLoginButton("md:hidden") : null; + const desktopAuthItem = logIn ? ( + {renderLoginButton()} ) : null; @@ -40,13 +41,14 @@ function TopView({ className="w-full overflow-visible bg-transparent" role="banner" aria-label={t("ariaLabels.homePageNavigationHeader")} + data-figma-node="5944-17779" > @@ -105,11 +108,10 @@ function TopView({ /** * Standard marketing / app top nav. - * Figma: "Navigation / Top" (Community-Rule-System, node 22078-808559). - * Below md: auto | 1fr | auto so the logo cannot paint over the cluster. - * From md: 1fr | auto | 1fr so the cluster sits on the same page midline - * as Folder-top (Create rule is wider than the logo, so a leftover-column - * center sits off-center). + * Figma: "Navigation / Top" (Community-Rule-System, node 5944:17779). + * Below md: logo | links (with Log in / Profile) | Create rule, so the mark + * cannot paint over the cluster. From md: 1fr | auto | 1fr so the cluster + * sits on the page midline. */ return ( <> @@ -121,13 +123,17 @@ function TopView({ className="relative z-50 w-full border-b border-[var(--border-color-default-tertiary)] bg-[var(--color-surface-default-primary)]" role="banner" aria-label={t("ariaLabels.mainNavigationHeader")} + data-figma-node="5944-17779" > diff --git a/app/components/sections/AskOrganizer/AskOrganizer.view.tsx b/app/components/sections/AskOrganizer/AskOrganizer.view.tsx index b64f052..4538111 100644 --- a/app/components/sections/AskOrganizer/AskOrganizer.view.tsx +++ b/app/components/sections/AskOrganizer/AskOrganizer.view.tsx @@ -44,6 +44,7 @@ function AskOrganizerView({ variant={lockupVariant} alignment={lockupAlignment} titleId={labelledBy} + headingLevel="h2" />
+ title={title} + subtitle={subtitle} + variant="feature" + linkText={linkText} + linkHref={linkHref} + titleId={labelledBy} + headingLevel="h2" + />
diff --git a/app/components/sections/HeroBanner/HeroBanner.container.tsx b/app/components/sections/HeroBanner/HeroBanner.container.tsx new file mode 100644 index 0000000..6805c4e --- /dev/null +++ b/app/components/sections/HeroBanner/HeroBanner.container.tsx @@ -0,0 +1,85 @@ +"use client"; + +/** + * Figma: "Section / Hero Banner" (18477:15473) + */ + +import { memo, useCallback, useEffect, useMemo, useState } from "react"; +import { useRouter } from "next/navigation"; +import { useMessages, useTranslation } from "../../../contexts/MessagesContext"; +import { fetchAuthSession } from "../../../../lib/create/api"; +import { prepareFreshCreateFlowEntrySync } from "../../../(app)/create/utils/prepareFreshCreateFlowEntry"; +import type { Category } from "../../cards/Rule"; +import type { ChipOption } from "../../controls/MultiSelect/MultiSelect.types"; +import HeroBannerView from "./HeroBanner.view"; +import type { HeroBannerProps } from "./HeroBanner.types"; + +function chipsFromLabels(labels: string[]): ChipOption[] { + return labels.map((label) => ({ + id: label.toLowerCase().replace(/[^a-z0-9]+/g, "-"), + label, + state: "unselected" as const, + })); +} + +const HeroBannerContainer = memo( + ({ title, subtitle, description }) => { + const router = useRouter(); + const messages = useMessages(); + const tHome = useTranslation("pages.home.heroBanner"); + const tHero = useTranslation("heroBanner"); + const [signedIn, setSignedIn] = useState(false); + + useEffect(() => { + let cancelled = false; + void fetchAuthSession() + .then(({ user }) => { + if (!cancelled) setSignedIn(Boolean(user)); + }) + .catch(() => { + /* guest fallback */ + }); + return () => { + cancelled = true; + }; + }, []); + + const handleCreateRule = useCallback(() => { + prepareFreshCreateFlowEntrySync({ signedIn }); + router.push("/create/informational"); + }, [router, signedIn]); + + const ruleCategories = useMemo(() => { + const groups = messages.heroBanner.ruleCard.categories; + return [ + groups.values, + groups.communication, + groups.membership, + groups.decisionMaking, + groups.conflictManagement, + ].map((group) => ({ + name: group.name, + chipOptions: chipsFromLabels(group.chips), + })); + }, [messages]); + + return ( + + ); + }, +); + +HeroBannerContainer.displayName = "HeroBanner"; + +export default HeroBannerContainer; diff --git a/app/components/sections/HeroBanner/HeroBanner.tsx b/app/components/sections/HeroBanner/HeroBanner.tsx deleted file mode 100644 index f285ca6..0000000 --- a/app/components/sections/HeroBanner/HeroBanner.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Figma: "Sections / Hero" (see registry) - */ - -import { memo } from "react"; -import Image from "next/image"; -import ContentLockup from "../../type/ContentLockup"; -import HeroDecor from "./HeroDecor"; -import { ASSETS, getAssetPath } from "../../../../lib/assetUtils"; - -/** - * Intrinsic dimensions of `public/assets/marketing/hero-image.png` (2560×1600, - * 16:10). Passed to `next/image` to reserve aspect ratio + drive responsive - * srcset generation. Actual rendered size is governed by `sizes`. - */ -const HERO_IMAGE_WIDTH = 2560; -const HERO_IMAGE_HEIGHT = 1600; - -interface HeroBannerProps { - title?: string; - subtitle?: string; - description?: string; - ctaText?: string; - ctaHref?: string; - imageAlt?: string; -} - -const HeroBanner = memo( - ({ - title, - subtitle, - description, - ctaText, - ctaHref, - imageAlt = "Hero illustration", - }) => { - return ( -
-
- {/* Frame container for content */} -
- {/* DECORATIONS (behind content) */} - - - {/* Content lockup - Large variant */} -
- -
- - {/* Hero Image Container */} -
- {imageAlt} -
-
-
-
- ); - }, -); - -HeroBanner.displayName = "HeroBanner"; - -export default HeroBanner; diff --git a/app/components/sections/HeroBanner/HeroBanner.types.ts b/app/components/sections/HeroBanner/HeroBanner.types.ts new file mode 100644 index 0000000..d536cc8 --- /dev/null +++ b/app/components/sections/HeroBanner/HeroBanner.types.ts @@ -0,0 +1,20 @@ +import type { Category } from "../../cards/Rule"; + +export interface HeroBannerProps { + title?: string; + subtitle?: string; + description?: string; +} + +export interface HeroBannerViewProps { + title: string; + subtitle: string; + description: string; + facilitatorCta: string; + facilitatorHref: string; + createRuleCta: string; + onCreateRule: () => void; + ruleTitle: string; + ruleDescription: string; + ruleCategories: Category[]; +} diff --git a/app/components/sections/HeroBanner/HeroBanner.view.tsx b/app/components/sections/HeroBanner/HeroBanner.view.tsx new file mode 100644 index 0000000..797a6d3 --- /dev/null +++ b/app/components/sections/HeroBanner/HeroBanner.view.tsx @@ -0,0 +1,125 @@ +"use client"; + +import { memo } from "react"; +import Button from "../../buttons/Button"; +import Rule from "../../cards/Rule"; +import ContentLockup from "../../type/ContentLockup"; +import { ASSETS, getAssetPath } from "../../../../lib/assetUtils"; +import HeroDecor from "./HeroDecor"; +import type { HeroBannerViewProps } from "./HeroBanner.types"; + +function HeroBannerView({ + title, + subtitle, + description, + facilitatorCta, + facilitatorHref, + createRuleCta, + onCreateRule, + ruleTitle, + ruleDescription, + ruleCategories, +}: HeroBannerViewProps) { + return ( +
+
+
+ + +
+ +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+ + } + /> +
+
+
+
+ ); +} + +HeroBannerView.displayName = "HeroBannerView"; + +export default memo(HeroBannerView); diff --git a/app/components/sections/HeroBanner/index.tsx b/app/components/sections/HeroBanner/index.tsx index 406f808..3491c0b 100644 --- a/app/components/sections/HeroBanner/index.tsx +++ b/app/components/sections/HeroBanner/index.tsx @@ -1 +1,2 @@ -export { default } from "./HeroBanner"; +export { default } from "./HeroBanner.container"; +export type { HeroBannerProps } from "./HeroBanner.types"; diff --git a/app/components/sections/LogoWall/LogoWall.container.tsx b/app/components/sections/LogoWall/LogoWall.container.tsx index c031175..a2350a7 100644 --- a/app/components/sections/LogoWall/LogoWall.container.tsx +++ b/app/components/sections/LogoWall/LogoWall.container.tsx @@ -1,9 +1,8 @@ "use client"; /** - * Figma: "Section / Logo Wall" (18847:22366) - * Index frames: 320–429 2×3 grid, 430–639 3×2 grid, 640+ space-between row. - * Logo heights from those instances (1× / 1.25 at lg / 1.5625 at xl). + * Figma: "Section / Logo Wall" (18483:18425) + * Home uses the three pilot marks (street medics, Food Not Bombs, Mutual Aid Colorado). */ import { memo, useMemo } from "react"; @@ -17,41 +16,20 @@ const LogoWallContainer = memo(({ logos, className = "" }) => { const defaultLogos = useMemo( () => [ + { + src: getAssetPath(partnerLogoPath("boulder-street-medics")), + alt: t("partners.boulderStreetMedics"), + size: "h-[48px] lg:h-[60px] xl:h-[74.6px]", + }, { src: getAssetPath(partnerLogoPath("food-not-bombs")), alt: t("partners.foodNotBombs"), size: "h-[48px] lg:h-[60px] xl:h-[74.6px]", - order: "sm:order-4 md:order-4", - }, - { - src: getAssetPath(partnerLogoPath("start-coop")), - alt: t("partners.startCoop"), - size: "h-[51.4px] lg:h-[64.2px] xl:h-[80.3px]", - order: "sm:order-1 md:order-3", - }, - { - src: getAssetPath(partnerLogoPath("metagov")), - alt: t("partners.metagov"), - size: "h-[26.2px] lg:h-[33px] xl:h-[41px]", - order: "sm:order-6 md:order-1", - }, - { - src: getAssetPath(partnerLogoPath("open-civics")), - alt: t("partners.openCivics"), - size: "h-[50.1px] lg:h-[61.1px] xl:h-[72px]", - order: "sm:order-5 md:order-2", }, { src: getAssetPath(partnerLogoPath("mutual-aid-co")), alt: t("partners.mutualAidCo"), size: "h-[51.1px] lg:h-[63.6px] xl:h-[79.5px]", - order: "sm:order-2 md:order-5", - }, - { - src: getAssetPath(partnerLogoPath("cu-boulder")), - alt: t("partners.cuBoulder"), - size: "h-[44.6px] lg:h-[55.8px] xl:h-[69.7px]", - order: "sm:order-3 md:order-6", }, ], [t], @@ -63,11 +41,7 @@ const LogoWallContainer = memo(({ logos, className = "" }) => { ); return ( - + ); }); diff --git a/app/components/sections/LogoWall/LogoWall.types.ts b/app/components/sections/LogoWall/LogoWall.types.ts index fb480e3..5687464 100644 --- a/app/components/sections/LogoWall/LogoWall.types.ts +++ b/app/components/sections/LogoWall/LogoWall.types.ts @@ -5,20 +5,17 @@ export interface LogoWallProps { width?: number; height?: number; size?: string; - order?: string; }>; className?: string; } export interface LogoWallViewProps { - heading: string; displayLogos: Array<{ src: string; alt: string; width?: number; height?: number; size?: string; - order?: string; }>; className: string; } diff --git a/app/components/sections/LogoWall/LogoWall.view.tsx b/app/components/sections/LogoWall/LogoWall.view.tsx index eb09009..2fee9a9 100644 --- a/app/components/sections/LogoWall/LogoWall.view.tsx +++ b/app/components/sections/LogoWall/LogoWall.view.tsx @@ -4,41 +4,31 @@ import { memo } from "react"; import Image from "next/image"; import type { LogoWallViewProps } from "./LogoWall.types"; -function LogoWallView({ - heading, - displayLogos, - className, -}: LogoWallViewProps) { +function LogoWallView({ displayLogos, className }: LogoWallViewProps) { return (
-
-

- {heading} -

-
-
- {displayLogos.map((logo, index) => ( -
- {logo.alt} -
- ))} -
+
+
+ {displayLogos.map((logo, index) => ( +
+ {logo.alt} +
+ ))}
diff --git a/app/components/type/ContentLockup/ContentLockup.container.tsx b/app/components/type/ContentLockup/ContentLockup.container.tsx index 6212390..9fc5092 100644 --- a/app/components/type/ContentLockup/ContentLockup.container.tsx +++ b/app/components/type/ContentLockup/ContentLockup.container.tsx @@ -22,6 +22,7 @@ const ContentLockupContainer = memo( alignment: alignmentProp = "center", titleId, titleContent, + headingLevel: headingLevelProp = "h1", }) => { const variant = variantProp; const alignment = alignmentProp; @@ -179,6 +180,7 @@ const ContentLockupContainer = memo( alignment={alignment} titleId={titleId} titleContent={titleContent} + headingLevel={headingLevelProp} styles={styles} /> ); diff --git a/app/components/type/ContentLockup/ContentLockup.types.ts b/app/components/type/ContentLockup/ContentLockup.types.ts index 6edee3f..953a4e3 100644 --- a/app/components/type/ContentLockup/ContentLockup.types.ts +++ b/app/components/type/ContentLockup/ContentLockup.types.ts @@ -13,6 +13,8 @@ export type ContentLockupVariantValue = export type ContentLockupAlignmentValue = "center" | "left"; +export type ContentLockupHeadingLevel = "h1" | "h2"; + export interface ContentLockupProps { title?: string; subtitle?: string; @@ -37,6 +39,12 @@ export interface ContentLockupProps { titleId?: string; /** Replaces the default title string when inline title markup is required. */ titleContent?: ReactNode; + /** + * Heading tag for the title. FeatureGrid and AskOrganizer use `h2` so each + * page keeps a single `h1`. + * @default "h1" + */ + headingLevel?: ContentLockupHeadingLevel; } export interface VariantStyle { @@ -72,5 +80,6 @@ export interface ContentLockupViewProps { alignment: "center" | "left"; titleId?: string; titleContent?: ReactNode; + headingLevel: ContentLockupHeadingLevel; styles: VariantStyle; } diff --git a/app/components/type/ContentLockup/ContentLockup.view.tsx b/app/components/type/ContentLockup/ContentLockup.view.tsx index c383faa..be9c6f4 100644 --- a/app/components/type/ContentLockup/ContentLockup.view.tsx +++ b/app/components/type/ContentLockup/ContentLockup.view.tsx @@ -19,8 +19,11 @@ function ContentLockupView({ alignment, titleId, titleContent, + headingLevel, styles, }: ContentLockupViewProps) { + const TitleTag = headingLevel; + return (
{variant === "ask" || @@ -43,9 +46,9 @@ function ContentLockupView({ }`} > {title ? ( -

+ {title} -

+ ) : null}
{subtitle ?

{subtitle}

: null} @@ -61,16 +64,16 @@ function ContentLockupView({ {/* Title container */}
{titleContent ? ( -

{titleContent} -

+ ) : title ? ( -

+ {title} -

+ ) : null} {variant === "hero" && ( <> diff --git a/docs/figma-component-registry.md b/docs/figma-component-registry.md index ce80cfc..46652c5 100644 --- a/docs/figma-component-registry.md +++ b/docs/figma-component-registry.md @@ -139,7 +139,7 @@ Inventory aligns with [**CR-104**](https://linear.app/community-rule/issue/CR-10 | Figma / product (typical) | Code (`app/components/sections/`) | Notes | | --- | --- | --- | -| Hero | **`HeroBanner/`** | **`HeroBanner.tsx`** + **`HeroDecor.tsx`** + **`index.tsx`** (presentational package). | +| Hero | **`HeroBanner/`** | **`container` / `view` / `types`** + **`HeroDecor.tsx`** + **`index.tsx`**. | | Content banner | **`ContentBanner.tsx`** | Single module at bucket root. | | Logo wall | **`LogoWall/`** | **`container` / `view` / `types`** + **`index.tsx`**. | | Card steps (SectionCardSteps) | **`CardSteps/`** | Composes **`cards/Step`** (Figma **Card / Step** — not **`progress/Stepper`**). | diff --git a/messages/en/components/featureGrid.json b/messages/en/components/featureGrid.json index d049d2d..84c470d 100644 --- a/messages/en/components/featureGrid.json +++ b/messages/en/components/featureGrid.json @@ -1,6 +1,6 @@ { - "_comment": "FeatureGrid component defaults (shared across pages). Explore the toolkit goes to /learn; feature cards stay non-interactive until destinations are decided.", - "linkText": "Explore the toolkit", + "_comment": "FeatureGrid component defaults (shared across pages). Explore the patterns goes to /learn; feature cards stay non-interactive until destinations are decided.", + "linkText": "Explore the patterns", "linkHref": "/learn", "ariaLabel": "Feature tools and services", "features": { diff --git a/messages/en/components/header.json b/messages/en/components/header.json index 6f2c3cf..5555233 100644 --- a/messages/en/components/header.json +++ b/messages/en/components/header.json @@ -1,6 +1,6 @@ { "navigation": { - "useCases": "Use cases", + "workshops": "Workshops", "learn": "Learn", "about": "About" }, diff --git a/messages/en/components/heroBanner.json b/messages/en/components/heroBanner.json index db371ef..0d67503 100644 --- a/messages/en/components/heroBanner.json +++ b/messages/en/components/heroBanner.json @@ -1,4 +1,48 @@ { - "_comment": "HeroBanner component defaults (shared across pages)", - "imageAlt": "Hero illustration" + "_comment": "HeroBanner defaults and the Home Rule card. Chip names follow the current deck and may change when redesigned decks are approved.", + "facilitatorCta": "Talk to a facilitator", + "facilitatorHref": "/workshops", + "createRuleCta": "Create rule", + "ruleCard": { + "title": "CommunityRule Creative Team", + "description": "We build and maintain CommunityRule for the communities that use it. It is free for every community that wants to write down how it decides.", + "categories": { + "values": { + "name": "Values", + "chips": [ + "Transparency", + "Forkability", + "Documentation", + "Maintenance", + "Voluntarism" + ] + }, + "communication": { + "name": "Communication", + "chips": [ + "GitHub / GitLab", + "Matrix / Element", + "Email distribution list", + "How we run meetings" + ] + }, + "membership": { + "name": "Membership", + "chips": ["Join by contributing", "Companions"] + }, + "decisionMaking": { + "name": "Decision-making", + "chips": [ + "Consensus", + "Lazy consensus", + "Do-ocracy", + "Changing this Rule" + ] + }, + "conflictManagement": { + "name": "Conflict management", + "chips": ["Conflict ladder", "Hearing panel", "Restorative practices"] + } + } + } } diff --git a/messages/en/components/logoWall.json b/messages/en/components/logoWall.json index 01d634c..b834964 100644 --- a/messages/en/components/logoWall.json +++ b/messages/en/components/logoWall.json @@ -1,11 +1,8 @@ { - "heading": "Trusted by leading cooperators", + "_comment": "Home Logo Wall — three pilot lettered marks, no heading. Medics alt uses Boulder Street Medics until the group confirms its public name.", "partners": { + "boulderStreetMedics": "Boulder Street Medics", "foodNotBombs": "Food Not Bombs", - "metagov": "Metagov", - "mutualAidCo": "Mutual Aid CO", - "openCivics": "Open Civics", - "startCoop": "Start COOP", - "cuBoulder": "CU Boulder" + "mutualAidCo": "Mutual Aid Colorado" } } diff --git a/messages/en/navigation.json b/messages/en/navigation.json index 2cfcc12..523289e 100644 --- a/messages/en/navigation.json +++ b/messages/en/navigation.json @@ -1,6 +1,6 @@ { "_comment": "Navigation items", - "useCases": "Use cases", + "workshops": "Workshops", "learn": "Learn", "about": "About" } diff --git a/messages/en/pages/home.json b/messages/en/pages/home.json index 7e712a9..8d42f87 100644 --- a/messages/en/pages/home.json +++ b/messages/en/pages/home.json @@ -1,48 +1,45 @@ { - "_comment": "Home page specific content", + "_comment": "Home page specific content. Card Steps 1 and 2 follow the Prototype frame and are pending design approval of one set.", "heroBanner": { - "title": "Collaborate", - "subtitle": "with clarity", - "description": "Help your community make important decisions in a way that reflects its unique values.", - "ctaText": "Learn how CommunityRule works", - "ctaHref": "/how-it-works" + "title": "Build structure", + "subtitle": "before you need it", + "description": "Strong communities agree on four things early. They settle how they talk, who belongs, how they decide and what happens when they disagree. Write yours down together." }, "cardSteps": { - "title": "How CommunityRule works", + "title": "How CommunityRule helps", "headingDesktopLine1": "How", "headingDesktopLine2": "CommunityRule", "headingDesktopLine3": "helps", "subtitle": "Here's a quick overview of the process, from start to finish.", "cards": { "card1": { - "text": "Document how your community makes decisions", - "_comment": "First step card" + "text": "See what has worked for other communities", + "_comment": "Pending design approval versus Section / Card Steps" }, "card2": { - "text": "Build an operating manual for a successful community", - "_comment": "Second step card" + "text": "Decide how you will decide, while things are calm", + "_comment": "Pending design approval versus Section / Card Steps" }, "card3": { - "text": "Get a link to your manual for your group to review and evolve", - "_comment": "Third step card" + "text": "Invite your members and change it together as you grow" } } }, "featureGrid": { "title": "Tools for the parts that get hard", - "subtitle": "Every group runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", + "subtitle": "Every community runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", "features": { "decisionMaking": { "title": "Choose how you decide", - "description": "Compare consensus, do-ocracy, and other patterns, then commit to one." + "description": "Compare consensus, do-ocracy and other patterns, then commit to one." }, "valuesAlignment": { "title": "Name your values", - "description": "Short exercises that surface what your group agrees on before it's tested." + "description": "Short exercises that surface what your community agrees on before it's tested." }, "membershipGuidance": { "title": "Define membership", - "description": "Spell out how people join, take on roles, and step away." + "description": "Spell out how people join, take on roles and step away." }, "conflictResolution": { "title": "Plan for conflict", @@ -56,9 +53,8 @@ "buttonText": "Ask an organizer" }, "ruleStack": { - "title": "Popular templates", - "subtitle": "These are popular patterns for making decisions in mutual aid and open source communities. You can use them as they are or as a starting place for customizing your own CommunityRule.", - "subtitleLg": "These are popular patterns for making decisions in communities with egalitarian values. You can use them as they are or as a starting place for customizing your own CommunityRule.", + "title": "Get templates that help your community run smoothly", + "subtitle": "Ways other communities make decisions. Use one as it is or change it to fit yours.", "cards": { "consensusClusters": { "title": "Consensus clusters", diff --git a/public/assets/logos/partners/boulder-street-medics.svg b/public/assets/logos/partners/boulder-street-medics.svg new file mode 100644 index 0000000..85ad605 --- /dev/null +++ b/public/assets/logos/partners/boulder-street-medics.svg @@ -0,0 +1,56 @@ + + + diff --git a/stories/sections/CardSteps.stories.js b/stories/sections/CardSteps.stories.js index ce43129..d332a97 100644 --- a/stories/sections/CardSteps.stories.js +++ b/stories/sections/CardSteps.stories.js @@ -32,22 +32,22 @@ export default { export const Default = { args: { - title: "How CommunityRule works", + title: "How CommunityRule helps", subtitle: "Here's a quick overview of the process, from start to finish.", headingDesktopLines: ["How", "CommunityRule", "helps"], steps: [ { - text: "Document how your community makes decisions", + text: "See what has worked for other communities", iconShape: "blob", iconColor: "green", }, { - text: "Build an operating manual for a successful community", + text: "Decide how you will decide, while things are calm", iconShape: "gear", iconColor: "purple", }, { - text: "Get a link to your manual for your group to review and evolve", + text: "Invite your members and change it together as you grow", iconShape: "star", iconColor: "orange", }, @@ -66,7 +66,7 @@ export const CustomContent = { iconColor: "green", }, { - text: "Collaborate with our team to create the perfect solution", + text: "Build structure with our team to create the perfect solution", iconShape: "gear", iconColor: "purple", }, diff --git a/stories/sections/FeatureGrid.stories.js b/stories/sections/FeatureGrid.stories.js index 3effe47..7f9093d 100644 --- a/stories/sections/FeatureGrid.stories.js +++ b/stories/sections/FeatureGrid.stories.js @@ -38,7 +38,7 @@ export const Default = { args: { title: "Tools for the parts that get hard", subtitle: - "Every group runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", + "Every community runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", }, parameters: { docs: { diff --git a/stories/sections/HeroBanner.stories.js b/stories/sections/HeroBanner.stories.js index d6362e5..67a0618 100644 --- a/stories/sections/HeroBanner.stories.js +++ b/stories/sections/HeroBanner.stories.js @@ -8,7 +8,7 @@ export default { docs: { description: { component: - "A responsive hero banner component that showcases the Community Rule branding and messaging. Adapts across multiple breakpoints with proper spacing, typography, and interactive elements. Includes background decorations and product demo integration.", + "Home hero: ContentLockup plus the team's Rule card. Below lg the CTA is Talk to a facilitator; from lg it is Create rule.", }, }, }, @@ -25,32 +25,22 @@ export default { control: { type: "text" }, description: "The description text", }, - ctaText: { - control: { type: "text" }, - description: "The call-to-action button text", - }, - ctaHref: { - control: { type: "text" }, - description: "The call-to-action button link", - }, }, tags: ["autodocs"], }; export const Default = { args: { - title: "Collaborate", - subtitle: "with clarity", + title: "Build structure", + subtitle: "before you need it", description: - "Help your community make important decisions in a way that reflects its unique values.", - ctaText: "Learn how Community Rule works", - ctaHref: "#", + "Strong communities agree on four things early. They settle how they talk, who belongs, how they decide and what happens when they disagree. Write yours down together.", }, parameters: { docs: { description: { story: - "Default hero banner with standard Community Rule messaging and branding.", + "Default home hero with the team Rule card and breakpoint-specific CTAs.", }, }, }, diff --git a/stories/sections/LogoWall.stories.js b/stories/sections/LogoWall.stories.js index 1173f07..f8de374 100644 --- a/stories/sections/LogoWall.stories.js +++ b/stories/sections/LogoWall.stories.js @@ -7,49 +7,17 @@ export default { layout: "fullscreen", docs: { description: { - component: `A responsive logo wall component that displays partner/sponsor logos in a grid layout. Features responsive breakpoints with different layouts and sizing for mobile, tablet, and desktop views. + component: `Home Logo Wall: three pilot marks (Boulder Street Medics, Food Not Bombs, Mutual Aid Colorado) with no heading. ## Responsive Behavior -- **Mobile**: 3 rows × 2 columns grid with 38.4px gaps -- **SM**: 2 rows × 3 columns grid with 38.4px gaps -- **MD+**: Single space-between row (no wrap) -- **LG**: 1.25× logo sizes, 48px gaps, 64px horizontal padding -- **XL**: 1.5625× logo sizes, 48px gaps, 160px horizontal padding - -## Animations & Transitions - -- **Opacity**: Partner logos render at 60% opacity to match Figma \`Section / Logo Wall\`. +- **Mobile**: stacked column +- **SM+**: wrapping row +- **MD+**: single space-between row ## Props -- **logos** (optional): Array of logo objects with src, alt, and size properties. If not provided, uses default partner logos. - -## Usage Examples - -### Custom Logos -\`\`\`jsx - -\`\`\` - -### Empty State -\`\`\`jsx - -\`\`\` -This will fall back to the default partner logos.`, +- **logos** (optional): Array of logo objects with src, alt, and size properties. If not provided, uses the three pilot marks.`, }, }, }, @@ -58,7 +26,7 @@ This will fall back to the default partner logos.`, logos: { control: "object", description: - "Array of logo objects with src, alt, and size properties. If not provided, uses default partner logos.", + "Array of logo objects with src, alt, and size properties. If not provided, uses the three pilot marks.", }, }, }; @@ -69,7 +37,7 @@ export const Default = { docs: { description: { story: - "Default LogoWall with all partner logos. Displays in a 2×3 grid on mobile, 3×2 grid from 430px, and a space-between row from 640px with Figma lockup sizes. Logos render at 60% opacity to match Figma.", + "Default LogoWall with the three pilot marks at Figma 60% opacity.", }, }, }, diff --git a/tests/components/AskOrganizer.test.tsx b/tests/components/AskOrganizer.test.tsx index 797ab97..3312fb3 100644 --- a/tests/components/AskOrganizer.test.tsx +++ b/tests/components/AskOrganizer.test.tsx @@ -40,9 +40,9 @@ componentTestSuite(config); describe("AskOrganizer (behavioral tests)", () => { it("renders title", () => { render(); - expect( - screen.getByRole("heading", { name: "Test Title" }), - ).toBeInTheDocument(); + const title = screen.getByRole("heading", { name: "Test Title" }); + expect(title).toBeInTheDocument(); + expect(title.tagName).toBe("H2"); }); it("renders subtitle when provided", () => { diff --git a/tests/components/FeatureGrid.test.tsx b/tests/components/FeatureGrid.test.tsx index 8bd50e9..1d5f03c 100644 --- a/tests/components/FeatureGrid.test.tsx +++ b/tests/components/FeatureGrid.test.tsx @@ -38,9 +38,9 @@ componentTestSuite(config); describe("FeatureGrid (behavioral tests)", () => { it("renders title and subtitle", () => { render(); - expect( - screen.getByRole("heading", { name: "Test Title" }), - ).toBeInTheDocument(); + const title = screen.getByRole("heading", { name: "Test Title" }); + expect(title).toBeInTheDocument(); + expect(title.tagName).toBe("H2"); expect(screen.getByText("Test Subtitle")).toBeInTheDocument(); expect( screen.queryByRole("heading", { name: "Test Subtitle" }), @@ -58,10 +58,10 @@ describe("FeatureGrid (behavioral tests)", () => { ).not.toBeInTheDocument(); }); - it("sends Explore the toolkit to the Learn page", () => { + it("sends Explore the patterns to the Learn page", () => { render(); expect( - screen.getByRole("link", { name: "Explore the toolkit" }), + screen.getByRole("link", { name: "Explore the patterns" }), ).toHaveAttribute("href", "/learn"); }); diff --git a/tests/components/HeroBanner.test.tsx b/tests/components/HeroBanner.test.tsx index 9db02b5..08bef00 100644 --- a/tests/components/HeroBanner.test.tsx +++ b/tests/components/HeroBanner.test.tsx @@ -20,8 +20,6 @@ const config: ComponentTestSuiteConfig = { optionalProps: { subtitle: "Subtitle", description: "Description", - ctaText: "CTA", - ctaHref: "/link", }, primaryRole: "region", testCases: { @@ -50,18 +48,29 @@ describe("HeroBanner (behavioral tests)", () => { ).toBeInTheDocument(); }); - it("renders hero image", () => { + it("renders the team Rule card instead of a hero photograph", () => { render(); expect( - screen.getByRole("img", { name: "Hero illustration" }), + screen.getByRole("heading", { name: "CommunityRule Creative Team" }), ).toBeInTheDocument(); + expect( + screen.queryByRole("img", { name: "Hero illustration" }), + ).not.toBeInTheDocument(); }); - it("renders CTA with the inverse (black) filled palette", () => { - render(); - const cta = screen.getAllByRole("link", { name: "Get Started" })[0]; + it("sends Talk to a facilitator to the workshops page", () => { + render(); + const cta = screen.getAllByRole("link", { name: "Talk to a facilitator" })[0]; + expect(cta).toHaveAttribute("href", "/workshops"); expect(cta?.className).toContain( "bg-[var(--color-surface-default-primary)]", ); }); + + it("renders Create rule as a button for large screens", () => { + render(); + expect( + screen.getAllByRole("button", { name: "Create rule" }).length, + ).toBeGreaterThan(0); + }); }); diff --git a/tests/components/Logo.test.tsx b/tests/components/Logo.test.tsx index 8d7e9a8..fa32283 100644 --- a/tests/components/Logo.test.tsx +++ b/tests/components/Logo.test.tsx @@ -79,10 +79,18 @@ describe("Logo (behavioral tests)", () => { expect(row?.className).toContain("h-[var(--space-1000)]"); }); - it("shows the folder-top wordmark without a breakpoint hide", () => { + it("hides the folder-top wordmark below md", () => { render(); const wordmark = screen.getByText("CommunityRule"); - expect(wordmark.className).not.toMatch(/\bhidden\b/); + expect(wordmark.className).toMatch(/\bhidden\b/); + expect(wordmark.className).toContain("md:block"); + }); + + it("hides the standard header wordmark below sm", () => { + render(); + const wordmark = screen.getByText("CommunityRule"); + expect(wordmark.className).toContain("hidden"); + expect(wordmark.className).toContain("sm:block"); }); it("renders with different size variants", () => { diff --git a/tests/components/Top.test.tsx b/tests/components/Top.test.tsx index 2e5734a..4861618 100644 --- a/tests/components/Top.test.tsx +++ b/tests/components/Top.test.tsx @@ -131,7 +131,7 @@ describe("Top header chrome", () => { ); expect( screen.getAllByRole("menuitem", { - name: /navigate to use cases page/i, + name: /navigate to workshops page/i, hidden: true, }), ).toHaveLength(1); @@ -146,7 +146,7 @@ describe("Top header chrome", () => { name: /go to your profile/i, hidden: true, }), - ).toHaveLength(1); + ).toHaveLength(2); expect( screen.getAllByRole("button", { name: /create a new rule/i, @@ -157,11 +157,18 @@ describe("Top header chrome", () => { } }); + it("points Workshops at /workshops", () => { + renderWithProviders(); + expect( + screen.getByRole("menuitem", { name: /navigate to workshops page/i }), + ).toHaveAttribute("href", "/workshops"); + }); + it("shows Profile instead of Log in when signed in", () => { renderWithProviders(); expect( - screen.getByRole("menuitem", { name: /go to your profile/i }), - ).toBeInTheDocument(); + screen.getAllByRole("menuitem", { name: /go to your profile/i }).length, + ).toBeGreaterThan(0); expect( screen.queryByRole("button", { name: /log in to your account/i }), ).not.toBeInTheDocument(); @@ -174,6 +181,7 @@ describe("Top header chrome", () => { const headerNav = document.querySelector("header nav"); expect(logo?.className).not.toMatch(/\bz-20\b/); expect(nav?.className).not.toMatch(/\babsolute\b/); + expect(headerNav?.className).toContain("flex"); expect(headerNav?.className).toContain( "md:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)]", ); @@ -183,9 +191,8 @@ describe("Top header chrome", () => { renderWithProviders(); const tab = document.querySelector(".HeaderTab"); const nav = document.querySelector("[data-top='nav']"); - expect(tab?.className).toContain("w-fit"); - expect(tab?.className).toContain("sm:w-auto"); - expect(tab?.className).toContain("sm:flex-1"); + expect(tab?.className).toContain("flex-1"); + expect(tab?.className).toContain("justify-between"); expect(nav?.className).toContain("md:absolute"); expect(nav?.className).toContain( "md:left-[calc(50vw-var(--space-400))]", @@ -193,16 +200,17 @@ describe("Top header chrome", () => { expect(nav?.className).not.toContain("ml-auto"); }); - it("keeps a single home nav cluster and shows the folder wordmark", () => { + it("keeps a single home nav cluster and hides the folder wordmark below md", () => { renderWithProviders(); expect( screen.getAllByRole("menuitem", { - name: /navigate to use cases page/i, + name: /navigate to workshops page/i, hidden: true, }), ).toHaveLength(1); const wordmark = screen.getByText("CommunityRule"); - expect(wordmark.className).not.toMatch(/\bhidden\b/); + expect(wordmark.className).toMatch(/\bhidden\b/); + expect(wordmark.className).toContain("md:block"); expect( screen.getByRole("button", { name: /create a new rule/i }).className, ).toContain("md:text-x-small-label"); diff --git a/tests/components/TopWithPathname.test.tsx b/tests/components/TopWithPathname.test.tsx index bb88a2d..132d680 100644 --- a/tests/components/TopWithPathname.test.tsx +++ b/tests/components/TopWithPathname.test.tsx @@ -25,8 +25,8 @@ describe("TopWithPathname", () => { it("renders Profile from the server session before /api/auth/session resolves", () => { renderWithProviders(); expect( - screen.getByRole("menuitem", { name: /go to your profile/i }), - ).toBeInTheDocument(); + screen.getAllByRole("menuitem", { name: /go to your profile/i }).length, + ).toBeGreaterThan(0); expect( screen.queryByRole("button", { name: /log in to your account/i }), ).not.toBeInTheDocument(); diff --git a/tests/components/type/ContentLockup.test.tsx b/tests/components/type/ContentLockup.test.tsx index d99ecb9..d19919f 100644 --- a/tests/components/type/ContentLockup.test.tsx +++ b/tests/components/type/ContentLockup.test.tsx @@ -83,4 +83,27 @@ describe("ContentLockup", () => { expect(link.className).not.toMatch(/focus:ring-2/); expect(link.className).toMatch(/focus-visible:outline/); }); + + it("renders the title as h2 when headingLevel is h2", () => { + renderWithProviders( + , + ); + + expect( + screen.getByRole("heading", { + level: 2, + name: "Tools for the parts that get hard", + }), + ).toBeInTheDocument(); + expect( + screen.queryByRole("heading", { + level: 1, + name: "Tools for the parts that get hard", + }), + ).not.toBeInTheDocument(); + }); }); diff --git a/tests/e2e/critical-journeys.spec.ts b/tests/e2e/critical-journeys.spec.ts index d41d8e4..534fe42 100644 --- a/tests/e2e/critical-journeys.spec.ts +++ b/tests/e2e/critical-journeys.spec.ts @@ -7,40 +7,40 @@ test.describe("Critical User Journeys", () => { test("complete user journey: learn about CommunityRule", async ({ page }) => { // 1. User lands on homepage - await expect(page.locator("text=Collaborate")).toBeVisible(); + await expect(page.locator("text=Build structure")).toBeVisible(); // 2. User reads hero section await expect( - page.locator("text=Help your community make important decisions"), + page.locator("text=Strong communities agree on four things early"), ).toBeVisible(); // 3. User clicks CTA to learn more const learnCta = page - .getByRole("link", { name: /Learn how CommunityRule works/i }) - .or(page.getByRole("button", { name: /Learn how CommunityRule works/i })) + .getByRole("link", { name: /Talk to a facilitator/i }) + .or(page.getByRole("button", { name: /Talk to a facilitator/i })) .first(); if ((await learnCta.count()) > 0 && (await learnCta.isVisible())) { await learnCta.click(); - await expect(page).toHaveURL(/\/how-it-works/); + await expect(page).toHaveURL(/\/workshops/); } // 4. User scrolls to CardSteps section (home) // Note: mobile title is one line; lg+ uses stacked "How / CommunityRule / helps" from home copy const howItWorksHeading = page.locator( - 'h2:has-text("How CommunityRule works"), h2:has-text("How"), h2:has-text("CommunityRule"), h2:has-text("helps")', + 'h2:has-text("How CommunityRule helps"), h2:has-text("How"), h2:has-text("CommunityRule"), h2:has-text("helps")', ); await expect(howItWorksHeading).toBeVisible(); // 5. User reads the process steps await expect( - page.locator("text=Document how your community makes decisions"), + page.locator("text=See what has worked for other communities"), ).toBeVisible(); await expect( - page.locator("text=Build an operating manual for a successful community"), + page.locator("text=Decide how you will decide, while things are calm"), ).toBeVisible(); await expect( page.locator( - "text=Get a link to your manual for your group to review and evolve", + "text=Invite your members and change it together as you grow", ), ).toBeVisible(); @@ -87,20 +87,22 @@ test.describe("Critical User Journeys", () => { // Check main sections are present await expect( - page.locator("h1, h2").filter({ hasText: "Collaborate" }), + page.locator("h1, h2").filter({ hasText: "Build structure" }), ).toBeVisible(); const howItWorksHeading = page.locator( - 'h2:has-text("How CommunityRule works"), h2:has-text("How CommunityRule helps")', + 'h2:has-text("How CommunityRule helps"), h2:has-text("How CommunityRule helps")', ); await expect(howItWorksHeading).toBeVisible(); await expect( - page.locator("h1").filter({ hasText: "Tools for the parts that get hard" }), + page.locator("h2").filter({ hasText: "Tools for the parts that get hard" }), ).toBeVisible(); // Check key components are rendered - await expect(page.locator('img[alt="Hero illustration"]')).toBeVisible(); + await expect( + page.getByRole("heading", { name: "CommunityRule Creative Team" }), + ).toBeVisible(); await expect(page.locator('img[alt="Food Not Bombs"]')).toBeVisible(); await expect(page.locator("text=Jo Freeman")).toBeVisible(); }); @@ -108,11 +110,11 @@ test.describe("Critical User Journeys", () => { test("feature grid section functionality", async ({ page }) => { // Check section header await expect( - page.locator('h1:has-text("Tools for the parts that get hard")'), + page.locator('h2:has-text("Tools for the parts that get hard")'), ).toBeVisible(); await expect( page.locator( - "text=Every group runs into the same knots. CommunityRule gives you a way through", + "text=Every community runs into the same knots. CommunityRule gives you a way through", ), ).toBeVisible(); @@ -127,12 +129,12 @@ test.describe("Critical User Journeys", () => { featureSection.locator("text=Plan for conflict"), ).toBeVisible(); - // Feature tiles are presentational — only the lockup "Explore the toolkit" is a link + // Feature tiles are presentational — only the lockup "Explore the patterns" is a link await expect( featureSection.locator('a[href="#decision-making"]'), ).toHaveCount(0); await expect( - featureSection.getByRole("link", { name: "Explore the toolkit" }), + featureSection.getByRole("link", { name: "Explore the patterns" }), ).toHaveAttribute("href", "/learn"); }); @@ -186,19 +188,19 @@ test.describe("Critical User Journeys", () => { // Test mobile viewport await page.setViewportSize({ width: 375, height: 667 }); await expect( - page.locator("h1, h2").filter({ hasText: "Collaborate" }), + page.locator("h1, h2").filter({ hasText: "Build structure" }), ).toBeVisible(); // Test tablet viewport await page.setViewportSize({ width: 768, height: 1024 }); await expect( - page.locator("h1, h2").filter({ hasText: "Collaborate" }), + page.locator("h1, h2").filter({ hasText: "Build structure" }), ).toBeVisible(); // Test desktop viewport await page.setViewportSize({ width: 1440, height: 900 }); await expect( - page.locator("h1, h2").filter({ hasText: "Collaborate" }), + page.locator("h1, h2").filter({ hasText: "Build structure" }), ).toBeVisible(); }); }); diff --git a/tests/e2e/edge-cases.spec.ts b/tests/e2e/edge-cases.spec.ts index 2e30098..37b4b4e 100644 --- a/tests/e2e/edge-cases.spec.ts +++ b/tests/e2e/edge-cases.spec.ts @@ -18,7 +18,7 @@ test.describe("Edge Cases and Error Scenarios", () => { await page.goto("/", { waitUntil: "domcontentloaded", timeout: 10000 }); // Page should still load eventually - await expect(page.locator("text=Collaborate")).toBeVisible({ + await expect(page.locator("text=Build structure")).toBeVisible({ timeout: 10000, }); }); @@ -38,7 +38,7 @@ test.describe("Edge Cases and Error Scenarios", () => { await expect(body).toBeVisible(); // Verify key content is still accessible - await expect(page.locator("text=Collaborate")).toBeVisible(); + await expect(page.locator("text=Build structure")).toBeVisible(); }); test("handles JavaScript errors gracefully", async ({ page }) => { @@ -59,30 +59,19 @@ test.describe("Edge Cases and Error Scenarios", () => { }); // Page should continue to function - await expect(page.locator("text=Collaborate")).toBeVisible(); + await expect(page.locator("text=Build structure")).toBeVisible(); - const learnCta = page - .getByRole("link", { name: /Learn how CommunityRule works/i }) - .or(page.getByRole("button", { name: /Learn how CommunityRule works/i })); - const ctaCount = await learnCta.count(); - let visibleCta = null; - - for (let i = 0; i < ctaCount; i++) { - const cta = learnCta.nth(i); - if (await cta.isVisible()) { - visibleCta = cta; - break; - } + const createCta = page.getByRole("button", { name: "Create rule" }); + const facilitatorCta = page.getByRole("link", { + name: /Talk to a facilitator/i, + }); + const visibleCreate = createCta.filter({ has: page.locator(":visible") }); + if ((await visibleCreate.count()) > 0) { + await visibleCreate.first().click(); + await expect(page).toHaveURL(/\/create/); + } else { + await expect(facilitatorCta.first()).toBeVisible(); } - - if (!visibleCta) { - throw new Error( - 'No visible "Learn how CommunityRule works" CTA found', - ); - } - - await visibleCta.click(); - await expect(page).toHaveURL(/\/how-it-works/); }); test("handles missing images gracefully", async ({ page }) => { @@ -96,29 +85,9 @@ test.describe("Edge Cases and Error Scenarios", () => { await page.goto("/", { waitUntil: "domcontentloaded" }); // Page should still function without images - await expect(page.locator("text=Collaborate")).toBeVisible(); - - const learnCta = page - .getByRole("link", { name: /Learn how CommunityRule works/i }) - .or(page.getByRole("button", { name: /Learn how CommunityRule works/i })); - const ctaCount = await learnCta.count(); - let visibleCta = null; - - for (let i = 0; i < ctaCount; i++) { - const cta = learnCta.nth(i); - if (await cta.isVisible()) { - visibleCta = cta; - break; - } - } - - if (!visibleCta) { - throw new Error( - 'No visible "Learn how CommunityRule works" CTA found', - ); - } - - await visibleCta.click(); - await expect(page).toHaveURL(/\/how-it-works/); + await expect(page.locator("text=Build structure")).toBeVisible(); + await expect( + page.getByRole("heading", { name: "CommunityRule Creative Team" }), + ).toBeVisible(); }); }); diff --git a/tests/pages/home.test.jsx b/tests/pages/home.test.jsx index 5b1756e..86a31d2 100644 --- a/tests/pages/home.test.jsx +++ b/tests/pages/home.test.jsx @@ -11,11 +11,11 @@ describe("Page", () => { render(); // Check that all main sections are rendered (using getAllByText since there are multiple instances) - expect(screen.getAllByText("Collaborate").length).toBeGreaterThan(0); - expect(screen.getAllByText("with clarity").length).toBeGreaterThan(0); + expect(screen.getAllByText("Build structure").length).toBeGreaterThan(0); + expect(screen.getAllByText("before you need it").length).toBeGreaterThan(0); expect( screen.getAllByText( - "Help your community make important decisions in a way that reflects its unique values.", + "Strong communities agree on four things early. They settle how they talk, who belongs, how they decide and what happens when they disagree. Write yours down together.", ).length, ).toBeGreaterThan(0); @@ -23,7 +23,7 @@ describe("Page", () => { // Check CardSteps section (using getAllByText since there are multiple instances) await waitFor(() => { expect( - screen.getAllByText("How CommunityRule works").length, + screen.getAllByText("How CommunityRule helps").length, ).toBeGreaterThan(0); }); expect( @@ -41,7 +41,7 @@ describe("Page", () => { }); expect( screen.getAllByText( - "Every group runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", + "Every community runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", ).length, ).toBeGreaterThan(0); @@ -58,20 +58,20 @@ describe("Page", () => { render(); // Check hero banner content (using getAllByText since there are multiple instances) - expect(screen.getAllByText("Collaborate").length).toBeGreaterThan(0); - expect(screen.getAllByText("with clarity").length).toBeGreaterThan(0); + expect(screen.getAllByText("Build structure").length).toBeGreaterThan(0); + expect(screen.getAllByText("before you need it").length).toBeGreaterThan(0); expect( screen.getAllByText( - "Help your community make important decisions in a way that reflects its unique values.", + "Strong communities agree on four things early. They settle how they talk, who belongs, how they decide and what happens when they disagree. Write yours down together.", ).length, ).toBeGreaterThan(0); expect( - screen.getAllByText("Learn how CommunityRule works").length, + screen.getAllByText("Talk to a facilitator").length, ).toBeGreaterThan(0); const learnLinks = screen.getAllByRole("link", { - name: "Learn how CommunityRule works", + name: "Talk to a facilitator", }); - expect(learnLinks[0]).toHaveAttribute("href", "/how-it-works"); + expect(learnLinks[0]).toHaveAttribute("href", "/workshops"); }); test("renders CardSteps section with correct data", async () => { @@ -80,7 +80,7 @@ describe("Page", () => { // Wait for dynamically imported CardSteps component to load await waitFor(() => { expect( - screen.getAllByText("How CommunityRule works").length, + screen.getAllByText("How CommunityRule helps").length, ).toBeGreaterThan(0); }); expect( @@ -91,16 +91,16 @@ describe("Page", () => { // Check individual card content (using getAllByText since there are multiple instances) expect( - screen.getAllByText("Document how your community makes decisions").length, + screen.getAllByText("See what has worked for other communities").length, ).toBeGreaterThan(0); expect( screen.getAllByText( - "Build an operating manual for a successful community", + "Decide how you will decide, while things are calm", ).length, ).toBeGreaterThan(0); expect( screen.getAllByText( - "Get a link to your manual for your group to review and evolve", + "Invite your members and change it together as you grow", ).length, ).toBeGreaterThan(0); }); @@ -117,11 +117,11 @@ describe("Page", () => { }); expect( screen.getAllByText( - "Every group runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", + "Every community runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", ).length, ).toBeGreaterThan(0); const learnMore = screen.getAllByRole("link", { - name: "Explore the toolkit", + name: "Explore the patterns", }); expect(learnMore[0]).toHaveAttribute("href", "/learn"); }); @@ -144,14 +144,14 @@ describe("Page", () => { // Check that all major components are present by looking for their content // HeroBanner - expect(screen.getAllByText("Collaborate").length).toBeGreaterThan(0); + expect(screen.getAllByText("Build structure").length).toBeGreaterThan(0); // Wait for dynamically imported components to load // LogoWall - should be present (even if just the component structure) // CardSteps await waitFor(() => { expect( - screen.getAllByText("How CommunityRule works").length, + screen.getAllByText("How CommunityRule helps").length, ).toBeGreaterThan(0); }); @@ -174,7 +174,7 @@ describe("Page", () => { test("has correct page structure", () => { render(); - const mainContainer = screen.getAllByText("Collaborate")[0].closest("div"); + const mainContainer = screen.getAllByText("Build structure")[0].closest("div"); expect(mainContainer).toBeInTheDocument(); }); @@ -183,7 +183,7 @@ describe("Page", () => { // Check CTA button in hero banner expect( - screen.getAllByText("Learn how CommunityRule works").length, + screen.getAllByText("Talk to a facilitator").length, ).toBeGreaterThan(0); // Check CTA button in ask organizer section @@ -196,7 +196,7 @@ describe("Page", () => { // Check main description (using getAllByText since there are multiple instances) expect( screen.getAllByText( - "Help your community make important decisions in a way that reflects its unique values.", + "Strong communities agree on four things early. They settle how they talk, who belongs, how they decide and what happens when they disagree. Write yours down together.", ).length, ).toBeGreaterThan(0); @@ -213,7 +213,7 @@ describe("Page", () => { await waitFor(() => { expect( screen.getAllByText( - "Every group runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", + "Every community runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", ).length, ).toBeGreaterThan(0); }); @@ -228,12 +228,12 @@ describe("Page", () => { render(); // Check all section titles (using getAllByText since there are multiple instances) - expect(screen.getAllByText("Collaborate").length).toBeGreaterThan(0); + expect(screen.getAllByText("Build structure").length).toBeGreaterThan(0); // Wait for dynamically imported components await waitFor(() => { expect( - screen.getAllByText("How CommunityRule works").length, + screen.getAllByText("How CommunityRule helps").length, ).toBeGreaterThan(0); }); await waitFor(() => { @@ -254,18 +254,18 @@ describe("Page", () => { await waitFor(() => { // Three Step tiles inside CardSteps expect( - screen.getAllByText("Document how your community makes decisions") + screen.getAllByText("See what has worked for other communities") .length, ).toBeGreaterThan(0); }); expect( screen.getAllByText( - "Build an operating manual for a successful community", + "Decide how you will decide, while things are calm", ).length, ).toBeGreaterThan(0); expect( screen.getAllByText( - "Get a link to your manual for your group to review and evolve", + "Invite your members and change it together as you grow", ).length, ).toBeGreaterThan(0); }); @@ -274,7 +274,7 @@ describe("Page", () => { render(); // Check subtitles (using getAllByText since there are multiple instances) - expect(screen.getAllByText("with clarity").length).toBeGreaterThan(0); + expect(screen.getAllByText("before you need it").length).toBeGreaterThan(0); // Wait for dynamically imported components await waitFor(() => { @@ -287,7 +287,7 @@ describe("Page", () => { await waitFor(() => { expect( screen.getAllByText( - "Every group runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", + "Every community runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", ).length, ).toBeGreaterThan(0); }); diff --git a/tests/pages/page-flow.test.jsx b/tests/pages/page-flow.test.jsx index 21c959b..7624696 100644 --- a/tests/pages/page-flow.test.jsx +++ b/tests/pages/page-flow.test.jsx @@ -28,33 +28,31 @@ describe("Page Flow Integration", () => { // Hero Banner section expect( - screen.getByRole("heading", { name: "Collaborate" }), + screen.getByRole("heading", { name: "Build structure" }), ).toBeInTheDocument(); expect( - screen.getByRole("heading", { name: "with clarity" }), + screen.getByRole("heading", { name: "before you need it" }), ).toBeInTheDocument(); expect( screen.getByText( - "Help your community make important decisions in a way that reflects its unique values.", + "Strong communities agree on four things early. They settle how they talk, who belongs, how they decide and what happens when they disagree. Write yours down together.", ), ).toBeInTheDocument(); const ctaButtons = screen.getAllByRole("link", { - name: "Learn how CommunityRule works", + name: "Talk to a facilitator", }); expect(ctaButtons.length).toBeGreaterThan(0); await waitFor(() => { expect(screen.getByAltText("Food Not Bombs")).toBeInTheDocument(); }); - expect(screen.getByAltText("Start COOP")).toBeInTheDocument(); - expect(screen.getByAltText("Metagov")).toBeInTheDocument(); - expect(screen.getByAltText("Open Civics")).toBeInTheDocument(); - expect(screen.getByAltText("Mutual Aid CO")).toBeInTheDocument(); - expect(screen.getByAltText("CU Boulder")).toBeInTheDocument(); + expect(screen.getByAltText("Boulder Street Medics")).toBeInTheDocument(); + expect(screen.getByAltText("Mutual Aid Colorado")).toBeInTheDocument(); + expect(screen.queryByAltText("Start COOP")).not.toBeInTheDocument(); await waitFor(() => { expect( - screen.getByRole("heading", { name: /How CommunityRule works/ }), + screen.getByRole("heading", { name: /How CommunityRule helps/ }), ).toBeInTheDocument(); }); expect( @@ -63,25 +61,27 @@ describe("Page Flow Integration", () => { ), ).toBeInTheDocument(); expect( - screen.getByText("Document how your community makes decisions"), + screen.getByText("See what has worked for other communities"), ).toBeInTheDocument(); expect( - screen.getByText("Build an operating manual for a successful community"), + screen.getByText("Decide how you will decide, while things are calm"), ).toBeInTheDocument(); expect( screen.getByText( - "Get a link to your manual for your group to review and evolve", + "Invite your members and change it together as you grow", ), ).toBeInTheDocument(); await waitFor(() => { - expect(screen.getByRole("heading", { name: "Consensus" })).toBeInTheDocument(); + expect( + screen.getByRole("heading", { name: "Devolution" }), + ).toBeInTheDocument(); }); expect( - screen.getByRole("heading", { name: "Do-ocracy" }), + screen.getByRole("heading", { name: "Consensus" }), ).toBeInTheDocument(); expect( - screen.getByRole("heading", { name: "Devolution" }), + screen.getByRole("heading", { name: "Do-ocracy" }), ).toBeInTheDocument(); expect( screen.getByRole("heading", { name: "Quadratic Governance" }), @@ -96,7 +96,7 @@ describe("Page Flow Integration", () => { }); expect( screen.getByText( - "Every group runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", + "Every community runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", ), ).toBeInTheDocument(); @@ -120,11 +120,11 @@ describe("Page Flow Integration", () => { renderPage(); const ctaLinks = screen.getAllByRole("link", { - name: "Learn how CommunityRule works", + name: "Talk to a facilitator", }); const ctaLink = ctaLinks[0]; expect(ctaLink).toBeInTheDocument(); - expect(ctaLink).toHaveAttribute("href", "/how-it-works"); + expect(ctaLink).toHaveAttribute("href", "/workshops"); await user.click(ctaLink); expect(ctaLink).toBeInTheDocument(); @@ -135,13 +135,13 @@ describe("Page Flow Integration", () => { await waitFor(() => { const cards = screen.getAllByText( - /Document how your community|Build an operating manual|Get a link to your manual/, + /See what has worked for other communities|Decide how you will decide|Invite your members and change it together/, ); expect(cards.length).toBeGreaterThan(0); }); const cards = screen.getAllByText( - /Document how your community|Build an operating manual|Get a link to your manual/, + /See what has worked for other communities|Decide how you will decide|Invite your members and change it together/, ); expect(cards.length).toBeGreaterThan(0); @@ -153,11 +153,11 @@ describe("Page Flow Integration", () => { renderPage(); await waitFor(() => { - expect(screen.getByText("Consensus")).toBeInTheDocument(); + expect(screen.getByText("Devolution")).toBeInTheDocument(); }); expect(screen.queryByText("Solidarity Network")).not.toBeInTheDocument(); - expect(screen.getByText("Do-ocracy")).toBeInTheDocument(); - expect(screen.getByText("Devolution")).toBeInTheDocument(); + expect(screen.getAllByText("Consensus").length).toBeGreaterThan(0); + expect(screen.getAllByText("Do-ocracy").length).toBeGreaterThan(0); expect(screen.getByText("Quadratic Governance")).toBeInTheDocument(); const seeAll = screen.getByRole("link", { name: "See all templates" }); @@ -189,7 +189,7 @@ describe("Page Flow Integration", () => { expect(headings.length).toBeGreaterThan(4); const mainContent = screen.getByText( - /Help your community make important decisions/, + /Strong communities agree on four things early/, ); expect(mainContent).toBeInTheDocument(); }); @@ -220,15 +220,15 @@ describe("Page Flow Integration", () => { renderPage(); expect( - screen.getByText(/Help your community make important decisions/), + screen.getByText(/Strong communities agree on four things early/), ).toBeInTheDocument(); await waitFor(() => { - expect(screen.getByText("How CommunityRule works")).toBeInTheDocument(); + expect(screen.getByText("How CommunityRule helps")).toBeInTheDocument(); }); await waitFor(() => { - expect(screen.getByText("Consensus")).toBeInTheDocument(); + expect(screen.getByText("Devolution")).toBeInTheDocument(); }); await waitFor(() => { diff --git a/tests/pages/user-journey.test.jsx b/tests/pages/user-journey.test.jsx index 886b5ce..91f2e31 100644 --- a/tests/pages/user-journey.test.jsx +++ b/tests/pages/user-journey.test.jsx @@ -38,16 +38,16 @@ describe("User Journey Integration", () => { renderPageWithFooter(); expect( - screen.getByText(/Help your community make important decisions/), + screen.getByText(/Strong communities agree on four things early/), ).toBeInTheDocument(); const learnLinks = screen.getAllByRole("link", { - name: "Learn how CommunityRule works", + name: "Talk to a facilitator", }); await user.click(learnLinks[0]); await waitFor(() => { - expect(screen.getByText("How CommunityRule works")).toBeInTheDocument(); + expect(screen.getByText("How CommunityRule helps")).toBeInTheDocument(); }); }); @@ -56,10 +56,10 @@ describe("User Journey Integration", () => { renderPage(); await waitFor(() => { - expect(screen.getByText("Consensus")).toBeInTheDocument(); + expect(screen.getByText("Devolution")).toBeInTheDocument(); }); - expect(screen.getByText("Do-ocracy")).toBeInTheDocument(); - expect(screen.getByText("Devolution")).toBeInTheDocument(); + expect(screen.getAllByText("Consensus").length).toBeGreaterThan(0); + expect(screen.getAllByText("Do-ocracy").length).toBeGreaterThan(0); expect(screen.getByText("Quadratic Governance")).toBeInTheDocument(); const seeHowLinks = screen.getAllByRole("link", { @@ -109,15 +109,15 @@ describe("User Journey Integration", () => { await waitFor(() => { expect( - screen.getByText("Document how your community makes decisions"), + screen.getByText("See what has worked for other communities"), ).toBeInTheDocument(); }); expect( - screen.getByText("Build an operating manual for a successful community"), + screen.getByText("Decide how you will decide, while things are calm"), ).toBeInTheDocument(); expect( screen.getByText( - "Get a link to your manual for your group to review and evolve", + "Invite your members and change it together as you grow", ), ).toBeInTheDocument(); @@ -156,7 +156,7 @@ describe("User Journey Integration", () => { }); expect( screen.getByText( - "Every group runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", + "Every community runs into the same knots. CommunityRule gives you a way through, and a written record of what you decided.", ), ).toBeInTheDocument(); @@ -173,11 +173,8 @@ describe("User Journey Integration", () => { const partnerLogos = logoImages.filter( (img) => img.alt?.includes("Food Not Bombs") || - img.alt?.includes("Start COOP") || - img.alt?.includes("Metagov") || - img.alt?.includes("Open Civics") || - img.alt?.includes("Mutual Aid CO") || - img.alt?.includes("CU Boulder"), + img.alt?.includes("Boulder Street Medics") || + img.alt?.includes("Mutual Aid Colorado"), ); expect(partnerLogos.length).toBeGreaterThan(0); }); @@ -193,11 +190,11 @@ describe("User Journey Integration", () => { test("user completes the full journey from discovery to action", async () => { renderPageWithFooter(); - expect(screen.getByText("Collaborate")).toBeInTheDocument(); - expect(screen.getByText("with clarity")).toBeInTheDocument(); + expect(screen.getByText("Build structure")).toBeInTheDocument(); + expect(screen.getByText("before you need it")).toBeInTheDocument(); await waitFor(() => { - expect(screen.getByText("How CommunityRule works")).toBeInTheDocument(); + expect(screen.getByText("How CommunityRule helps")).toBeInTheDocument(); }); await waitFor(() => { diff --git a/tests/unit/LogoWall.test.jsx b/tests/unit/LogoWall.test.jsx index 6099f87..c485e7b 100644 --- a/tests/unit/LogoWall.test.jsx +++ b/tests/unit/LogoWall.test.jsx @@ -7,16 +7,16 @@ afterEach(() => { cleanup(); }); describe("LogoWall Component", () => { - test("renders with default logos", () => { + test("renders the three pilot marks", () => { render(); - // Check for default logos + expect(screen.getByAltText("Boulder Street Medics")).toBeInTheDocument(); expect(screen.getByAltText("Food Not Bombs")).toBeInTheDocument(); - expect(screen.getByAltText("Start COOP")).toBeInTheDocument(); - expect(screen.getByAltText("Metagov")).toBeInTheDocument(); - expect(screen.getByAltText("Open Civics")).toBeInTheDocument(); - expect(screen.getByAltText("Mutual Aid CO")).toBeInTheDocument(); - expect(screen.getByAltText("CU Boulder")).toBeInTheDocument(); + expect(screen.getByAltText("Mutual Aid Colorado")).toBeInTheDocument(); + expect(screen.queryByAltText("Start COOP")).not.toBeInTheDocument(); + expect(screen.queryByAltText("Metagov")).not.toBeInTheDocument(); + expect(screen.queryByAltText("Open Civics")).not.toBeInTheDocument(); + expect(screen.queryByAltText("CU Boulder")).not.toBeInTheDocument(); }); test("renders with custom logos", () => { @@ -25,13 +25,11 @@ describe("LogoWall Component", () => { src: "assets/custom1.png", alt: "Custom Logo 1", size: "h-8", - order: "order-1", }, { src: "assets/custom2.png", alt: "Custom Logo 2", size: "h-10", - order: "order-2", }, ]; @@ -62,19 +60,15 @@ describe("LogoWall Component", () => { ); }); - test("applies responsive grid layout", () => { + test("lays out the three marks in a wrapping row", () => { render(); - const grid = document.querySelector( - '[class*="grid grid-cols-2 grid-rows-3"]', - ); - expect(grid).toBeInTheDocument(); - expect(grid).toHaveClass( - "sm:grid-cols-3", - "sm:grid-rows-2", - "md:flex", - "md:justify-between", + const row = document.querySelector('[class*="flex w-full flex-col"]'); + expect(row).toBeInTheDocument(); + expect(row).toHaveClass( + "sm:flex-row", "md:flex-nowrap", + "md:justify-between", "gap-[38.4px]", ); }); @@ -97,7 +91,6 @@ describe("LogoWall Component", () => { test("handles empty logos array", () => { render(); - // Should fall back to default logos expect(screen.getByAltText("Food Not Bombs")).toBeInTheDocument(); }); @@ -112,15 +105,14 @@ describe("LogoWall Component", () => { ).toBe(0); }); - test("renders with proper semantic structure", () => { + test("renders with proper semantic structure and no heading", () => { render(); const section = document.querySelector("section"); expect(section).toBeInTheDocument(); - expect( - screen.getByText("Trusted by leading cooperators"), - ).toBeInTheDocument(); + screen.queryByText("Trusted by leading cooperators"), + ).not.toBeInTheDocument(); }); test("renders partner logos at Figma 60% opacity", () => { @@ -136,22 +128,15 @@ describe("LogoWall Component", () => { const logos = screen.getAllByRole("img"); logos.forEach((logo) => { - // Next.js Image attributes are not rendered as HTML attributes in JSDOM - // Just verify the images are present expect(logo).toBeInTheDocument(); }); }); - test("prioritizes first two logos", () => { + test("marks the section with the Figma node id", () => { render(); - const logos = screen.getAllByRole("img"); - const foodNotBombsLogo = logos.find((img) => img.alt === "Food Not Bombs"); - const startCoopLogo = logos.find((img) => img.alt === "Start COOP"); - - // Next.js Image priority attribute is not rendered as HTML attribute in JSDOM - // Just verify the logos are present - expect(foodNotBombsLogo).toBeInTheDocument(); - expect(startCoopLogo).toBeInTheDocument(); + expect( + document.querySelector('[data-figma-node="18483-18425"]'), + ).toBeInTheDocument(); }); }); diff --git a/tests/unit/RuleStack.test.jsx b/tests/unit/RuleStack.test.jsx index f7604de..7034555 100644 --- a/tests/unit/RuleStack.test.jsx +++ b/tests/unit/RuleStack.test.jsx @@ -90,7 +90,9 @@ describe("RuleStack Component", () => { test("defaults to home rule stack heading copy when namespace omitted", () => { render(); const heading = screen.getByRole("heading", { level: 2 }); - expect(heading.textContent).toMatch(/Popular templates/); + expect(heading.textContent).toMatch( + /Get templates that help your community run smoothly/, + ); }); test("renders four featured governance template cards on the home row", async () => { From ea85be2287163e208f640afa4bb688fb0e6def67 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Tue, 3 Nov 2026 16:04:57 -0700 Subject: [PATCH 2/3] Replace Create-rule header portraits with real PNGs so the circles paint. The old SVG wrappers nested corrupt PNGs, so browsers fetched the files but showed empty rings. Co-authored-by: Cursor --- .gitignore | 3 + app/layout.tsx | 6 +- docs/guides/static-assets.md | 2 +- lib/assetUtils.ts | 6 +- public/assets/marketing/avatar-1.png | Bin 0 -> 20796 bytes public/assets/marketing/avatar-1.svg | 3 - public/assets/marketing/avatar-2.png | Bin 0 -> 30921 bytes public/assets/marketing/avatar-2.svg | 3 - public/assets/marketing/avatar-3.png | Bin 0 -> 24266 bytes public/assets/marketing/avatar-3.svg | 3 - stories/asset/Avatar.stories.js | 34 +++++----- stories/asset/AvatarContainer.stories.js | 76 +++++++++++------------ tests/components/Avatar.test.tsx | 6 +- 13 files changed, 68 insertions(+), 74 deletions(-) create mode 100644 public/assets/marketing/avatar-1.png delete mode 100644 public/assets/marketing/avatar-1.svg create mode 100644 public/assets/marketing/avatar-2.png delete mode 100644 public/assets/marketing/avatar-2.svg create mode 100644 public/assets/marketing/avatar-3.png delete mode 100644 public/assets/marketing/avatar-3.svg diff --git a/.gitignore b/.gitignore index 067a097..e6b2b3a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,9 @@ npm-cache/ # Marketing book cover (raster; see docs/guides/static-assets.md) !public/assets/marketing/community-rules-cover.png +# Header Create-rule portraits +!public/assets/marketing/avatar-*.png + # Open Graph 1200×630 share images (`npm run generate:og-images`) !public/assets/marketing/og-image.png !public/assets/marketing/og-image-templates.png diff --git a/app/layout.tsx b/app/layout.tsx index 3e78027..7fdbd2d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -123,19 +123,19 @@ export default function RootLayout({ children }: { children: ReactNode }) { rel="preload" as="image" href={getAssetPath(ASSETS.AVATAR_1)} - type="image/svg+xml" + type="image/png" /> $qt5ZQZVgp90IX4%;*$;#gQdwGBU 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 My85}Sb4q9e08Q+0Y5)KL literal 0 HcmV?d00001 diff --git a/public/assets/marketing/avatar-1.svg b/public/assets/marketing/avatar-1.svg deleted file mode 100644 index 4bfbd40..0000000 --- a/public/assets/marketing/avatar-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/public/assets/marketing/avatar-2.png b/public/assets/marketing/avatar-2.png new file mode 100644 index 0000000000000000000000000000000000000000..9100a691f588a7b731669a180b7798da3e11fe54 GIT binary patch literal 30921 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 xA^zX4y}kBB;s40K-2d - - diff --git a/public/assets/marketing/avatar-3.png b/public/assets/marketing/avatar-3.png new file mode 100644 index 0000000000000000000000000000000000000000..026635a38930f597631ff5f5738e042a3c8c4e7a GIT binary patch literal 24266 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 literal 0 HcmV?d00001 diff --git a/public/assets/marketing/avatar-3.svg b/public/assets/marketing/avatar-3.svg deleted file mode 100644 index b019939..0000000 --- a/public/assets/marketing/avatar-3.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/stories/asset/Avatar.stories.js b/stories/asset/Avatar.stories.js index 2c5a09c..f7d7491 100644 --- a/stories/asset/Avatar.stories.js +++ b/stories/asset/Avatar.stories.js @@ -55,7 +55,7 @@ export default { export const Default = { args: { - src: "assets/marketing/avatar-1.svg", + src: "assets/marketing/avatar-1.png", alt: "User Avatar", size: "medium", }, @@ -63,7 +63,7 @@ export const Default = { export const Sizes = { args: { - src: "assets/marketing/avatar-1.svg", + src: "assets/marketing/avatar-1.png", alt: "User Avatar", }, render: (args) => ( @@ -146,9 +146,9 @@ export const DifferentAvatars = { render: (args) => (
- - - + + +
), @@ -168,36 +168,36 @@ export const AllSizesWithDifferentAvatars = {

Small Size

- - - + + +

Medium Size

- - - + + +

Large Size

- - - + + +

XLarge Size

- - - + + +
diff --git a/stories/asset/AvatarContainer.stories.js b/stories/asset/AvatarContainer.stories.js index 2ab7cab..3c48851 100644 --- a/stories/asset/AvatarContainer.stories.js +++ b/stories/asset/AvatarContainer.stories.js @@ -33,9 +33,9 @@ export const Default = { }, render: (args) => ( - - - + + + ), }; @@ -47,36 +47,36 @@ export const Sizes = {

Small Size

- - - + + +

Medium Size

- - - + + +

Large Size

- - - + + +

XLarge Size

- - - + + +
@@ -98,38 +98,38 @@ export const DifferentGroupSizes = {

2 Users

- - + +

3 Users

- - - + + +

4 Users

- - - - + + + +

5 Users

- - - - - + + + + +
@@ -158,9 +158,9 @@ export const InContext = {

Team Members

- - - + + + 3 team members
@@ -172,10 +172,10 @@ export const InContext = {
- - - - + + + + 4 contributors
@@ -185,8 +185,8 @@ export const InContext = {

Small Team

- - + + 2 members
diff --git a/tests/components/Avatar.test.tsx b/tests/components/Avatar.test.tsx index 733c0cc..54b9b09 100644 --- a/tests/components/Avatar.test.tsx +++ b/tests/components/Avatar.test.tsx @@ -15,7 +15,7 @@ import { type AvatarProps = React.ComponentProps; const baseProps: AvatarProps = { - src: "/assets/marketing/avatar-1.svg", + src: "/assets/marketing/avatar-1.png", alt: "User avatar", size: "medium", }; @@ -46,7 +46,7 @@ describe("Avatar (behavioral tests)", () => { it("does not show initials while the image is loading", () => { render( , @@ -57,7 +57,7 @@ describe("Avatar (behavioral tests)", () => { it("applies the small photo ring after the image loads", () => { const { container } = render( , From 5bd49eaed8e8df8e3d4a3f0d3798bb0c536b3365 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Tue, 3 Nov 2026 16:05:06 -0700 Subject: [PATCH 3/3] Keep the three logo-wall marks in a centered cluster instead of stretching them across the page. The space-between row was drawn for six logos; three marks looked abandoned at the edges. Co-authored-by: Cursor --- app/components/sections/LogoWall/LogoWall.view.tsx | 4 ++-- stories/sections/LogoWall.stories.js | 6 +++--- tests/unit/LogoWall.test.jsx | 7 +++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/components/sections/LogoWall/LogoWall.view.tsx b/app/components/sections/LogoWall/LogoWall.view.tsx index 2fee9a9..c10ba10 100644 --- a/app/components/sections/LogoWall/LogoWall.view.tsx +++ b/app/components/sections/LogoWall/LogoWall.view.tsx @@ -11,11 +11,11 @@ function LogoWallView({ displayLogos, className }: LogoWallViewProps) { data-figma-node="18483-18425" >
-
+
{displayLogos.map((logo, index) => (
{ ); }); - test("lays out the three marks in a wrapping row", () => { + test("lays out the three marks as a centered cluster", () => { render(); const row = document.querySelector('[class*="flex w-full flex-col"]'); expect(row).toBeInTheDocument(); expect(row).toHaveClass( + "justify-center", "sm:flex-row", + "sm:justify-center", "md:flex-nowrap", - "md:justify-between", "gap-[38.4px]", ); + expect(row).not.toHaveClass("md:justify-between"); + expect(row).not.toHaveClass("sm:justify-around"); }); test("renders logos with correct attributes", () => {