Implement use cases page

This commit is contained in:
adilallo
2026-05-17 21:41:54 -06:00
parent b6b9b63608
commit 450da4d8ab
78 changed files with 1870 additions and 118 deletions
@@ -10,14 +10,28 @@ const RuleStack = dynamic(() => import("../../components/sections/RuleStack"), {
ssr: true,
});
type MarketingRuleStackSectionProps = {
translationNamespace?: string;
twoColumnsFromMd?: boolean;
};
/**
* Server-loaded “Popular templates” row so the first paint has card data without a client fetch.
*/
export async function MarketingRuleStackSection() {
export async function MarketingRuleStackSection({
translationNamespace,
twoColumnsFromMd,
}: MarketingRuleStackSectionProps = {}) {
const rows = await listRuleTemplatesFromDb();
const initialGridEntries = gridEntriesForSlugOrderWithCatalogFallback(
rows,
GOVERNANCE_TEMPLATE_HOME_SLUGS,
);
return <RuleStack initialGridEntries={initialGridEntries} />;
return (
<RuleStack
initialGridEntries={initialGridEntries}
translationNamespace={translationNamespace}
twoColumnsFromMd={twoColumnsFromMd}
/>
);
}
+177
View File
@@ -0,0 +1,177 @@
import type { Metadata } from "next";
import dynamic from "next/dynamic";
import { Suspense } from "react";
import messages from "../../../messages/en/index";
import { getAllBlogPosts } from "../../../lib/content";
import PageHeader from "../../components/type/PageHeader";
import CaseStudy from "../../components/cards/CaseStudy";
import UseCasesOrgs from "../../components/sections/UseCasesOrgs";
import QuoteBlock from "../../components/sections/QuoteBlock";
import Groups from "../../components/sections/Groups";
import type { GroupsItem } from "../../components/sections/Groups";
import TripleStep from "../../components/type/TripleStep";
import TripleTextBlock from "../../components/type/TripleTextBlock";
import type { TripleTextBlockColumn } from "../../components/type/TripleTextBlock";
import AskOrganizer from "../../components/sections/AskOrganizer";
import { MarketingRuleStackSection } from "../_components/MarketingRuleStackSection";
import { getAssetPath, vectorMarkPath } from "../../../lib/assetUtils";
const RelatedArticles = dynamic(
() => import("../../components/sections/RelatedArticles"),
{
loading: () => (
<section className="min-h-[400px] bg-black py-[var(--spacing-scale-032)]" />
),
ssr: true,
},
);
function asArray<T>(value: unknown): T[] {
return Array.isArray(value) ? value : [];
}
/** Matches `pages.useCases.groups.items` order ↔ `public/assets/vector/*.svg`. */
const USE_CASES_GROUP_VECTOR_SLUGS = [
"worker-coop",
"mutual-aid",
"open-source",
"dao",
] as const;
const USE_CASES_RELATED_SENTINEL_SLUG = "__use-cases-page__";
export async function generateMetadata(): Promise<Metadata> {
const title = messages.metadata.useCases.title;
const description = messages.metadata.useCases.description;
const keywords = messages.metadata.useCases.keywords;
return {
title,
description,
keywords,
openGraph: {
title,
description,
type: "website",
siteName: "CommunityRule",
},
};
}
export default function UseCasesPage() {
const page = messages.pages.useCases;
const tripleColumns = asArray<TripleTextBlockColumn>(page.tripleTextBlock.columns);
const groupItemsRaw = asArray<{ title: string; description: string }>(
page.groups.items,
);
const groupItems: GroupsItem[] = groupItemsRaw.map((item, index) => ({
...item,
icon: (
/* eslint-disable-next-line @next/next/no-img-element -- small vector marks from `public/assets/vector` */
<img
alt=""
aria-hidden
className="block size-9 shrink-0 object-contain"
height={36}
src={getAssetPath(
vectorMarkPath(
USE_CASES_GROUP_VECTOR_SLUGS[index] ?? USE_CASES_GROUP_VECTOR_SLUGS[0],
),
)}
width={36}
/>
),
}));
const askOrganizerData = {
title: page.askOrganizer.title,
subtitle: page.askOrganizer.subtitle,
buttonText: page.askOrganizer.buttonText,
};
const allPosts = getAllBlogPosts();
const relatedPosts = allPosts.slice(0, 8);
const slugOrder = allPosts.map((p) => p.slug);
const tripleStepSteps = asArray<{ title: string; body: string }>(
page.tripleStep.steps,
);
return (
<div className="min-h-screen bg-[var(--color-surface-default-primary)]">
<PageHeader
title={page.pageHeader.title}
headingAlign="center"
sectionMinimal
singleLineTitleFromLg
/>
<UseCasesOrgs>
<CaseStudy
surface="lavender"
imageAlt={page.caseStudyTiles.mutualAidColoradoAlt}
/>
<CaseStudy
surface="neutral"
imageAlt={page.caseStudyTiles.foodNotBombsAlt}
/>
<CaseStudy
surface="rose"
imageAlt={page.caseStudyTiles.boulderCountyStreetMedicsAlt}
/>
</UseCasesOrgs>
<QuoteBlock
variant="statement"
id="use-cases-statement-quote"
quote={page.quote.paragraph1}
quoteSecondary={page.quote.paragraph2}
/>
<Groups title={page.groups.title} items={groupItems} />
<TripleStep
heading={page.tripleStep.heading}
steps={tripleStepSteps}
ctaText={page.tripleStep.ctaText}
ctaHref={page.tripleStep.ctaHref}
/>
<div className="bg-[var(--color-surface-default-primary)]">
<Suspense
fallback={
<section className="min-h-[400px] py-[var(--spacing-scale-032)]" />
}
>
<MarketingRuleStackSection
translationNamespace="pages.useCases.ruleStack"
twoColumnsFromMd
/>
</Suspense>
</div>
<TripleTextBlock
layoutPreset="useCases"
title={page.tripleTextBlock.title}
ctaText={page.tripleTextBlock.ctaText}
ctaHref={page.tripleTextBlock.ctaHref}
columns={tripleColumns}
/>
<div className="bg-black">
<RelatedArticles
relatedPosts={relatedPosts}
currentPostSlug={USE_CASES_RELATED_SENTINEL_SLUG}
slugOrder={slugOrder}
variant="useCases"
/>
</div>
<section className="bg-[var(--color-surface-default-primary)]">
<AskOrganizer {...askOrganizerData} />
</section>
</div>
);
}
+9
View File
@@ -13,6 +13,9 @@ import ImageGlyphIcon from "./image.svg";
import LogOutIcon from "./log_out.svg";
import MailIcon from "./mail.svg";
import MarkdownCopyIcon from "./markdown_copy.svg";
import Numeric1CircleIcon from "./numeric-1-circle.svg";
import Numeric2CircleIcon from "./numeric-2-circle.svg";
import Numeric3CircleIcon from "./numeric-3-circle.svg";
import NumberIcon from "./number.svg";
import PictureAsPdfIcon from "./picture_as_pdf.svg";
import TagsIcon from "./tags.svg";
@@ -31,6 +34,9 @@ export const ICON_NAME_OPTIONS = [
"log_out",
"mail",
"markdown_copy",
"numeric_1_circle",
"numeric_2_circle",
"numeric_3_circle",
"number",
"picture_as_pdf",
"tags",
@@ -57,6 +63,9 @@ const iconMap: Record<IconName, SvgComponent> = {
log_out: LogOutIcon,
mail: MailIcon,
markdown_copy: MarkdownCopyIcon,
numeric_1_circle: Numeric1CircleIcon,
numeric_2_circle: Numeric2CircleIcon,
numeric_3_circle: Numeric3CircleIcon,
number: NumberIcon,
picture_as_pdf: PictureAsPdfIcon,
tags: TagsIcon,
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 7V9H12V17H14V7H10ZM12 2C13.3132 2 14.6136 2.25866 15.8268 2.7612C17.0401 3.26375 18.1425 4.00035 19.0711 4.92893C19.9997 5.85752 20.7362 6.95991 21.2388 8.17317C21.7413 9.38642 22 10.6868 22 12C22 14.6522 20.9464 17.1957 19.0711 19.0711C17.1957 20.9464 14.6522 22 12 22C10.6868 22 9.38642 21.7413 8.17317 21.2388C6.95991 20.7362 5.85752 19.9997 4.92893 19.0711C3.05357 17.1957 2 14.6522 2 12C2 9.34784 3.05357 6.8043 4.92893 4.92893C6.8043 3.05357 9.34784 2 12 2Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 596 B

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 7V9H13V11H11C10.4696 11 9.96086 11.2107 9.58579 11.5858C9.21071 11.9609 9 12.4696 9 13V17H11H15V15H11V13H13C13.5304 13 14.0391 12.7893 14.4142 12.4142C14.7893 12.0391 15 11.5304 15 11V9C15 8.46957 14.7893 7.96086 14.4142 7.58579C14.0391 7.21071 13.5304 7 13 7H9ZM12 2C13.3132 2 14.6136 2.25866 15.8268 2.7612C17.0401 3.26375 18.1425 4.00035 19.0711 4.92893C19.9997 5.85752 20.7362 6.95991 21.2388 8.17317C21.7413 9.38642 22 10.6868 22 12C22 14.6522 20.9464 17.1957 19.0711 19.0711C17.1957 20.9464 14.6522 22 12 22C10.6868 22 9.38642 21.7413 8.17317 21.2388C6.95991 20.7362 5.85752 19.9997 4.92893 19.0711C3.05357 17.1957 2 14.6522 2 12C2 9.34784 3.05357 6.8043 4.92893 4.92893C6.8043 3.05357 9.34784 2 12 2Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 839 B

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 15V13.5C15 13.1022 14.842 12.7206 14.5607 12.4393C14.2794 12.158 13.8978 12 13.5 12C13.8978 12 14.2794 11.842 14.5607 11.5607C14.842 11.2794 15 10.8978 15 10.5V9C15 7.89 14.1 7 13 7H9V9H13V11H11V13H13V15H9V17H13C13.5304 17 14.0391 16.7893 14.4142 16.4142C14.7893 16.0391 15 15.5304 15 15ZM12 2C13.3132 2 14.6136 2.25866 15.8268 2.7612C17.0401 3.26375 18.1425 4.00035 19.0711 4.92893C19.9997 5.85752 20.7362 6.95991 21.2388 8.17317C21.7413 9.38642 22 10.6868 22 12C22 14.6522 20.9464 17.1957 19.0711 19.0711C17.1957 20.9464 14.6522 22 12 22C10.6868 22 9.38642 21.7413 8.17317 21.2388C6.95991 20.7362 5.85752 19.9997 4.92893 19.0711C3.05357 17.1957 2 14.6522 2 12C2 9.34784 3.05357 6.8043 4.92893 4.92893C6.8043 3.05357 9.34784 2 12 2Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 866 B

@@ -0,0 +1,16 @@
"use client";
import { memo } from "react";
import CaseStudyView from "./CaseStudy.view";
import type { CaseStudyProps } from "./CaseStudy.types";
/**
* Figma: Section org lockup ([22112-871524](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22112-871524)): **Card / CaseStudy** — MAC vector (`assets/case-study/`), FNB/BCSM rasters (**2199332352** / **32353**).
*/
const CaseStudyContainer = memo<CaseStudyProps>((props) => {
return <CaseStudyView {...props} />;
});
CaseStudyContainer.displayName = "CaseStudy";
export default CaseStudyContainer;
@@ -0,0 +1,16 @@
import type { ReactNode } from "react";
export const CASE_STUDY_SURFACE_OPTIONS = ["lavender", "neutral", "rose"] as const;
export type CaseStudySurfaceValue = (typeof CASE_STUDY_SURFACE_OPTIONS)[number];
export interface CaseStudyProps {
surface: CaseStudySurfaceValue;
/**
* Alt text for built-in raster art (`public/assets/use-cases/`) when **`visual`** is omitted.
*/
imageAlt?: string;
/** Overrides built-in raster with custom slot content when provided. */
visual?: ReactNode;
className?: string;
}
@@ -0,0 +1,57 @@
"use client";
import Image from "next/image";
import { memo } from "react";
import type { CaseStudyProps } from "./CaseStudy.types";
const SURFACE_CLASS: Record<CaseStudyProps["surface"], string> = {
lavender: "bg-[var(--color-surface-invert-brand-lavender)]",
neutral: "bg-[var(--color-surface-invert-secondary)]",
rose: "bg-[var(--color-surface-invert-brand-red)]",
};
/** Default art per tile: PNG composites (FNB/BCSM) or vector Mutual Aid logo. */
const SURFACE_ART: Record<CaseStudyProps["surface"], string> = {
lavender: "/assets/case-study/case-study-mutual-aid.svg",
neutral: "/assets/use-cases/case-study-food-not-bombs.png",
rose: "/assets/use-cases/case-study-boulder-county-street-medics.png",
};
/** Figma: ~23px corner (“Card / CaseStudy” shells). */
const CASE_TILE_RADIUS_CLASS = "rounded-[23.093px]";
function CaseStudyView({
surface,
imageAlt = "",
visual,
className = "",
}: CaseStudyProps) {
return (
<div
data-figma-node="21993-32352"
className={`relative flex h-[305px] w-[305px] shrink-0 overflow-hidden ${CASE_TILE_RADIUS_CLASS} ${SURFACE_CLASS[surface]} ${className}`.trim()}
>
{visual ? (
<div className="flex size-full items-center justify-center p-2">{visual}</div>
) : (
<Image
src={SURFACE_ART[surface]}
alt={imageAlt}
width={305}
height={305}
unoptimized={
SURFACE_ART[surface].endsWith(".svg") ? true : undefined
}
className={`pointer-events-none select-none ${
surface === "lavender" ? "object-contain object-center" : "object-cover"
}`}
draggable={false}
/>
)}
</div>
);
}
CaseStudyView.displayName = "CaseStudyView";
export default memo(CaseStudyView);
+3
View File
@@ -0,0 +1,3 @@
export { default } from "./CaseStudy.container";
export type { CaseStudyProps, CaseStudySurfaceValue } from "./CaseStudy.types";
export { CASE_STUDY_SURFACE_OPTIONS } from "./CaseStudy.types";
+8 -2
View File
@@ -1,16 +1,20 @@
"use client";
import { memo } from "react";
import { memo, useId } from "react";
import { IconView } from "./Icon.view";
import type { IconProps } from "./Icon.types";
const IconContainer = memo<IconProps>(
({ icon, title, description, className = "", onClick }) => {
({ icon, title, description, className = "", onClick, interactive: interactiveProp = true }) => {
const layoutTitleId = useId();
const handleClick = () => {
if (!interactiveProp) return;
if (onClick) onClick();
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (!interactiveProp) return;
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
handleClick();
@@ -23,6 +27,8 @@ const IconContainer = memo<IconProps>(
title={title}
description={description}
className={className}
interactive={interactiveProp}
layoutTitleId={layoutTitleId}
onClick={handleClick}
onKeyDown={handleKeyDown}
/>
+8
View File
@@ -4,6 +4,11 @@ export interface IconProps {
description: string;
className?: string;
onClick?: () => void;
/**
* When false, renders a static tile (no button semantics or focus ring).
* @default true
*/
interactive?: boolean;
}
export interface IconViewProps {
@@ -11,6 +16,9 @@ export interface IconViewProps {
title: string;
description: string;
className: string;
interactive: boolean;
/** Stable id for `aria-labelledby` when `interactive` is false. */
layoutTitleId: string;
onClick: () => void;
onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
}
+22 -11
View File
@@ -7,30 +7,41 @@ export function IconView({
title,
description,
className,
interactive,
layoutTitleId,
onClick,
onKeyDown,
}: IconViewProps) {
const interactionClass = interactive
? "cursor-pointer transition-all duration-200 hover:scale-[1.02] hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-content-default-brand-primary)] focus:ring-offset-2"
: "cursor-default";
return (
<div
className={`border border-[var(--color-border-default-primary)] flex flex-col h-[350px] items-start justify-between p-[var(--measures-spacing-020)] relative w-[288px] bg-transparent cursor-pointer transition-all duration-200 hover:scale-[1.02] hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-content-default-brand-primary)] focus:ring-offset-2 ${className}`}
tabIndex={0}
role="button"
aria-label={`${title}: ${description}`}
onClick={onClick}
onKeyDown={onKeyDown}
data-figma-node="22084-859659"
className={`relative flex h-[350px] w-full min-w-[240px] max-w-[480px] flex-col items-start justify-between border border-solid border-[var(--color-border-default-secondary)] bg-transparent p-[var(--measures-spacing-020)] ${interactionClass} ${className}`}
tabIndex={interactive ? 0 : undefined}
role={interactive ? "button" : "article"}
aria-label={interactive ? `${title}: ${description}` : undefined}
aria-labelledby={interactive ? undefined : layoutTitleId}
onClick={interactive ? onClick : undefined}
onKeyDown={interactive ? onKeyDown : undefined}
>
{/* Icon */}
<div className="shrink-0 w-[36px] h-[36px] flex items-center justify-center">
<div className="flex h-9 w-9 shrink-0 items-center justify-center">
{icon}
</div>
{/* Title - Centered with auto space above and below */}
<h3 className="font-inter font-normal text-[32px] leading-[36px] text-[var(--color-content-default-primary)] w-full">
{/* Title — Figma XX Large / Label (32 / 36) */}
<h3
id={interactive ? undefined : layoutTitleId}
className="w-full text-left font-inter text-[32px] font-normal leading-[36px] text-[var(--color-content-default-primary)]"
>
{title}
</h3>
{/* Description */}
<p className="font-inter font-medium text-[10px] leading-[14px] uppercase text-[var(--color-content-default-primary)] w-full">
{/* Body: X Small / Paragraph (12/16) per Figma; 14/20 on md<lg only (Section 22084-859062) */}
<p className="w-full text-left font-inter font-normal text-[length:var(--text-x-small-paragraph)] leading-[length:var(--text-x-small-paragraph--line-height)] text-[var(--color-content-default-primary)] md:text-[length:var(--text-small-paragraph)] md:leading-[length:var(--text-small-paragraph--line-height)] lg:text-[length:var(--text-x-small-paragraph)] lg:leading-[length:var(--text-x-small-paragraph--line-height)]">
{description}
</p>
</div>
@@ -43,6 +43,7 @@ const RuleContainer = memo<RuleProps>(
bottomStatusLabel,
bottomLinks,
recommended = false,
templateGridFigmaShell = false,
}) => {
const size = sizeProp ?? "L";
@@ -98,6 +99,7 @@ const RuleContainer = memo<RuleProps>(
bottomStatusLabel={bottomStatusLabel}
bottomLinks={bottomLinks}
recommended={recommended}
templateGridFigmaShell={templateGridFigmaShell}
/>
);
},
+5
View File
@@ -66,6 +66,10 @@ export interface RuleProps {
* `expanded` — Figma `22142:898446` compact `Card / Rule` only.
*/
recommended?: boolean;
/**
* Marketing **GovernanceTemplateGrid** / RuleStack shell (Figma [22085:860413](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-860413&m=dev); card shell **18375:22616**).
*/
templateGridFigmaShell?: boolean;
}
export interface RuleViewProps {
@@ -90,4 +94,5 @@ export interface RuleViewProps {
bottomStatusLabel?: string;
bottomLinks?: RuleBottomLink[];
recommended?: boolean;
templateGridFigmaShell?: boolean;
}
+52 -16
View File
@@ -30,6 +30,7 @@ export function RuleView({
bottomStatusLabel,
bottomLinks,
recommended = false,
templateGridFigmaShell = false,
}: RuleViewProps) {
const t = useTranslation("ruleCard");
const ariaLabel = t("ariaLabel")?.replace("{title}", title) || title;
@@ -84,7 +85,13 @@ export function RuleView({
// Logo/Icon dimensions (inner circle) after Figma header `pl-1 pr-2 py-2` in icon cell
// (Card / Rule — e.g. `22143:900771` / `19706:12110`); outer column width holds padding + this.
const logoSize = 103; // `next/image` prop; actual box comes from `logoContainerClass`
const logoContainerClass = `
const logoContainerClass = templateGridFigmaShell
? `
max-[639px]:size-[56px]
min-[640px]:max-[1023px]:size-[64px]
min-[1024px]:size-[88px]
`
: `
max-[639px]:size-[56px]
min-[640px]:max-[1023px]:size-[64px]
min-[1024px]:max-[1439px]:size-[56px]
@@ -93,22 +100,51 @@ export function RuleView({
// Title typography - use CSS responsive classes
const showRecommendedTag = recommended && !expanded;
const titleClass = `
const titleClass = templateGridFigmaShell
? `
max-[639px]:font-inter max-[639px]:font-bold max-[639px]:text-[20px] max-[639px]:leading-[28px]
min-[640px]:max-[1023px]:font-bricolage-grotesque min-[640px]:max-[1023px]:font-bold min-[640px]:max-[1023px]:text-[28px] min-[640px]:max-[1023px]:leading-[36px]
min-[1024px]:max-[1439px]:font-bricolage-grotesque min-[1024px]:max-[1439px]:font-extrabold min-[1024px]:max-[1439px]:text-[36px] min-[1024px]:max-[1439px]:leading-[44px]
min-[1440px]:font-bricolage-grotesque min-[1440px]:font-extrabold min-[1440px]:text-[36px] min-[1440px]:leading-[44px]
`
: `
max-[639px]:font-inter max-[639px]:font-bold max-[639px]:text-[20px] max-[639px]:leading-[28px]
min-[640px]:max-[1023px]:font-bricolage-grotesque min-[640px]:max-[1023px]:font-bold min-[640px]:max-[1023px]:text-[28px] min-[640px]:max-[1023px]:leading-[36px]
min-[1024px]:max-[1439px]:font-bricolage-grotesque min-[1024px]:max-[1439px]:font-bold min-[1024px]:max-[1439px]:text-[24px] min-[1024px]:max-[1439px]:leading-[32px]
min-[1440px]:font-bricolage-grotesque min-[1440px]:font-extrabold min-[1440px]:text-[36px] min-[1440px]:leading-[44px]
`;
// Description typography
const descriptionClass = isLarge
? "font-inter font-medium text-[18px] leading-[24px]"
: isMedium
? "font-inter font-medium text-[14px] leading-[16px]"
? templateGridFigmaShell
? "font-inter font-medium text-[14px] leading-[16px] min-[1024px]:max-[1439px]:text-[18px] min-[1024px]:max-[1439px]:leading-[24px]"
: "font-inter font-medium text-[14px] leading-[16px]"
: isSmall
? "font-inter font-medium text-[14px] leading-[16px]" // S: 14px, medium, Inter
: "font-inter font-medium text-[12px] leading-[14px]"; // XS: 12px, medium, Inter
const headerIconCellClass = templateGridFigmaShell
? `
flex shrink-0 items-center justify-center
pl-[4px] pr-[8px] py-[8px]
max-[639px]:w-[72px]
min-[640px]:max-[1023px]:w-[80px]
min-[1024px]:max-[1439px]:w-[130px]
min-[1440px]:w-[119px]
`
: `
flex shrink-0 items-center justify-center
pl-[4px] pr-[8px] py-[8px]
max-[639px]:w-[72px]
min-[640px]:max-[1023px]:w-[80px]
min-[1024px]:w-[119px]
`;
const titleColumnMinHClass = templateGridFigmaShell
? "min-h-[72px] min-[640px]:min-h-[80px] min-[1024px]:max-[1439px]:min-h-[136px] min-[1440px]:min-h-[136px]"
: "min-h-[72px] min-[640px]:min-h-[80px] min-[1024px]:min-h-[88px] min-[1440px]:min-h-[136px]";
// Render logo/icon
const renderLogo = () => {
if (logoUrl) {
@@ -236,15 +272,7 @@ export function RuleView({
"
>
{renderLogo() && (
<div
className="
flex shrink-0 items-center justify-center
pl-[4px] pr-[8px] py-[8px]
max-[639px]:w-[72px]
min-[640px]:max-[1023px]:w-[80px]
min-[1024px]:w-[119px]
"
>
<div className={headerIconCellClass}>
{renderLogo()}
</div>
)}
@@ -252,7 +280,7 @@ export function RuleView({
<div
className={`
flex min-w-0 flex-1 flex-col justify-center
min-h-[72px] min-[640px]:min-h-[80px] min-[1024px]:min-h-[88px] min-[1440px]:min-h-[136px]
${titleColumnMinHClass}
border-l border-solid border-[var(--color-content-invert-primary)]
`}
>
@@ -410,9 +438,17 @@ export function RuleView({
) : (
/* Collapsed State: Description */
description && (
<div className="flex items-center justify-center relative shrink-0 w-full">
<div
className={
templateGridFigmaShell
? "relative flex w-full shrink-0 items-center justify-start"
: "relative flex w-full shrink-0 items-center justify-center"
}
>
<p
className={`${descriptionClass} cursor-inherit text-[var(--color-content-invert-primary)] flex-1`}
className={`${descriptionClass} cursor-inherit text-[var(--color-content-invert-primary)] ${
templateGridFigmaShell ? "w-full text-left" : "flex-1"
}`}
>
{description}
</p>
+1 -1
View File
@@ -137,7 +137,7 @@ const Footer = memo(() => {
md:gap-[var(--spacing-scale-032)]"
>
<Link
href="#"
href="/use-cases"
className={`w-full text-left ${primaryLinkClass} md:w-auto md:text-right`}
>
{t("navigation.useCases")}
@@ -17,14 +17,9 @@ type MenuClusterSize = "X Small" | "Small" | "Medium" | "Large" | "X Large";
/** Map responsive `NavSize` breakpoints to Figma menu item sizes (shared by nav links + login). */
const NAV_SIZE_TO_MENU_ITEM_SIZE: Record<NavSize, MenuClusterSize> = {
default: "Small",
xsmall: "X Small",
xsmallUseCases: "X Small",
home: "X Small",
homeMd: "Medium",
homeUseCases: "Small",
large: "Large",
largeUseCases: "Large",
homeXlarge: "X Large",
xlarge: "X Large",
};
@@ -77,7 +72,7 @@ const TopContainer = memo<TopProps>(
// Navigation items with translations
const navigationItems = [
{ href: "#", text: t("navigation.useCases"), extraPadding: true },
{ href: "/use-cases", text: t("navigation.useCases"), extraPadding: true },
{ href: "/learn", text: t("navigation.learn") },
{ href: "/about", text: t("navigation.about") },
];
@@ -134,7 +129,7 @@ const TopContainer = memo<TopProps>(
// folderTop: inverse mode (black text) for smallest breakpoints (xsmall/home)
// folderTop: default mode (yellow text) for 640px+ breakpoints (homeMd/large/homeXlarge/xlarge)
// false folderTop: always default mode (yellow text on dark background)
const isSmallBreakpoint = size === "xsmall" || size === "home";
const isSmallBreakpoint = size === "xsmall";
const mode = folderTop && isSmallBreakpoint ? "inverse" : "default";
const label = loggedIn ? t("buttons.profile") : t("buttons.logIn");
+1 -5
View File
@@ -9,15 +9,11 @@ export interface TopProps {
logIn?: boolean;
}
/** Breakpoint slot passed from {@link Top.view} into nav render helpers. */
export type NavSize =
| "default"
| "xsmall"
| "xsmallUseCases"
| "home"
| "homeMd"
| "homeUseCases"
| "large"
| "largeUseCases"
| "homeXlarge"
| "xlarge";
@@ -32,6 +32,7 @@ const VARIANT_STYLES: Record<
},
};
/** Figma **Section/AskOrganizer** [18116:15960](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=18116-15960&m=dev) (`lg` shell + type + button). */
const AskOrganizerContainer = memo<AskOrganizerProps>(
({
title,
@@ -28,6 +28,7 @@ function AskOrganizerView({
aria-labelledby={labelledBy}
aria-label={labelledBy ? undefined : ariaLabel}
tabIndex={-1}
data-figma-node="18116-15960"
>
<div className={`flex flex-col ${contentGap}`}>
{/* Content Lockup */}
@@ -41,13 +42,15 @@ function AskOrganizerView({
/>
{/* Button */}
<div className={buttonContainerClass}>
<div
className={`${buttonContainerClass} flex-wrap gap-y-[var(--spacing-scale-016)]`}
>
<Button
{...(buttonHref ? { href: buttonHref } : {})}
size="large"
buttonType="filled"
palette={variant === "inverse" ? "inverse" : "default"}
className="md:!px-[var(--spacing-scale-020)] md:!py-[var(--spacing-scale-012)] md:!text-[24px] md:!leading-[28px]"
className="!px-[var(--spacing-scale-016)] !py-[var(--spacing-scale-012)]"
onClick={onContactClick}
ariaLabel={ariaLabel}
data-testid="ask-organizer-cta"
@@ -10,11 +10,17 @@ import type { GovernanceTemplateCatalogEntry } from "../../../../lib/templates/g
export interface GovernanceTemplateGridProps {
entries: GovernanceTemplateCatalogEntry[];
onTemplateClick: (_slug: string) => void;
/**
* When true, use project **`md`** (640px) for a 2-column grid (e.g. `/use-cases`).
* Default keeps the template shell break at **768px**.
*/
twoColumnsFromMd?: boolean;
}
export function GovernanceTemplateGrid({
entries,
onTemplateClick,
twoColumnsFromMd = false,
}: GovernanceTemplateGridProps) {
const [isMounted, setIsMounted] = useState(false);
@@ -44,13 +50,21 @@ export function GovernanceTemplateGrid({
: "M"
: "M";
return (
<div
className={`
const gridLayoutClasses = twoColumnsFromMd
? `
flex flex-col gap-[18px]
md:grid md:grid-cols-2 md:gap-[18px]
lg:gap-[24px]
`
: `
flex flex-col gap-[18px]
min-[768px]:grid min-[768px]:grid-cols-2 min-[768px]:gap-[18px]
min-[1024px]:gap-[24px]
`}
`;
return (
<div
className={gridLayoutClasses}
>
{entries.map((card) => (
<Rule
@@ -58,19 +72,20 @@ export function GovernanceTemplateGrid({
title={card.title}
description={card.description}
recommended={card.recommended === true}
templateGridFigmaShell
size={cardSize}
className={`
select-none
cursor-pointer
max-[639px]:rounded-[var(--measures-radius-200,8px)]
min-[640px]:max-[1023px]:rounded-[var(--measures-radius-300,12px)]
min-[1024px]:rounded-[var(--radius-measures-radius-small)]
min-[1024px]:rounded-[var(--radius-measures-radius-large)]
max-[639px]:pb-[24px] max-[639px]:pt-[12px] max-[639px]:px-[12px]
min-[640px]:max-[1023px]:p-[24px]
min-[1024px]:max-[1439px]:p-[16px]
min-[1024px]:max-[1439px]:p-[24px]
min-[1440px]:p-[24px]
max-[1023px]:gap-[18px]
min-[1024px]:max-[1439px]:gap-[12px]
min-[1024px]:max-[1439px]:gap-[10px]
min-[1440px]:gap-[10px]
`}
icon={
@@ -84,7 +99,7 @@ export function GovernanceTemplateGrid({
cursor-inherit
max-[639px]:w-[40px] max-[639px]:h-[40px]
min-[640px]:max-[1023px]:w-[56px] min-[640px]:max-[1023px]:h-[56px]
min-[1024px]:max-[1439px]:w-[56px] min-[1024px]:max-[1439px]:h-[56px]
min-[1024px]:max-[1439px]:w-[90px] min-[1024px]:max-[1439px]:h-[90px]
min-[1440px]:w-[90px] min-[1440px]:h-[90px]
"
/>
@@ -1,14 +1,28 @@
/**
* Placeholder grid matching GovernanceTemplateGrid layout (loading state).
*/
export function GovernanceTemplateGridSkeleton({ count }: { count: number }) {
return (
<div
className="
export function GovernanceTemplateGridSkeleton({
count,
twoColumnsFromMd = false,
}: {
count: number;
twoColumnsFromMd?: boolean;
}) {
const gridLayoutClasses = twoColumnsFromMd
? `
flex flex-col gap-[18px]
md:grid md:grid-cols-2 md:gap-[18px]
lg:gap-[24px]
`
: `
flex flex-col gap-[18px]
min-[768px]:grid min-[768px]:grid-cols-2 min-[768px]:gap-[18px]
min-[1024px]:gap-[24px]
"
`;
return (
<div
className={gridLayoutClasses}
aria-busy
aria-label="Loading templates"
>
@@ -0,0 +1,27 @@
"use client";
import { memo, useId } from "react";
import GroupsView from "./Groups.view";
import type { GroupsProps } from "./Groups.types";
/**
* Figma: **Section** instance [**22085-860411**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-860411&m=dev) (`xl`: **Scale/160** horizontal padding);
* Card group ref [**22084-859062**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22084-859062&m=dev); legacy **22084-859470**.
*/
const GroupsContainer = memo<GroupsProps>(({ title, items, className = "" }) => {
const reactId = useId();
const headingId = `${reactId}-groups-heading`;
return (
<GroupsView
title={title}
items={items}
headingId={headingId}
className={className}
/>
);
});
GroupsContainer.displayName = "Groups";
export default GroupsContainer;
@@ -0,0 +1,17 @@
import type { ReactNode } from "react";
export interface GroupsItem {
icon: ReactNode;
title: string;
description: string;
}
export interface GroupsProps {
title: string;
items: GroupsItem[];
className?: string;
}
export interface GroupsViewProps extends GroupsProps {
headingId: string;
}
@@ -0,0 +1,44 @@
"use client";
import { memo } from "react";
import Icon from "../../cards/Icon";
import type { GroupsViewProps } from "./Groups.types";
function GroupsView({ title, items, headingId, className = "" }: GroupsViewProps) {
return (
<section
data-figma-node="22085-860411"
aria-labelledby={headingId}
className={`bg-transparent px-0 py-[var(--spacing-scale-064)] lg:px-[var(--spacing-scale-064)] xl:px-[var(--spacing-scale-160)] ${className}`.trim()}
>
<div className="mx-auto flex w-full max-w-[560px] flex-col items-center gap-[var(--spacing-scale-032)] md:max-w-[1280px] md:gap-[var(--spacing-scale-048)]">
<h2
id={headingId}
className="w-full shrink-0 text-center font-bricolage-grotesque text-[28px] font-bold leading-9 text-[var(--color-content-default-primary)] md:text-[32px] md:leading-10 lg:text-[40px] lg:leading-[52px]"
>
{title}
</h2>
<div className="flex w-full shrink-0 flex-col bg-[var(--color-surface-default-primary)] max-md:[&>*+*]:-mt-px md:grid md:grid-cols-2 md:gap-px md:bg-[var(--color-border-default-primary)] md:[&>*+*]:mt-0 lg:grid-cols-4">
{items.map((item, index) => (
<div
key={`${item.title}-${index}`}
className="flex min-h-[350px] min-w-0 shrink-0 justify-center bg-[var(--color-surface-default-primary)] md:justify-stretch"
>
<Icon
icon={item.icon}
title={item.title}
description={item.description}
interactive={false}
className="w-full min-w-0 max-w-none shrink-0"
/>
</div>
))}
</div>
</div>
</section>
);
}
GroupsView.displayName = "GroupsView";
export default memo(GroupsView);
+2
View File
@@ -0,0 +1,2 @@
export { default } from "./Groups.container";
export type { GroupsProps, GroupsItem } from "./Groups.types";
@@ -5,7 +5,7 @@ import { logger } from "../../../../lib/logger";
import QuoteBlockView from "./QuoteBlock.view";
import type { QuoteBlockProps, VariantConfig } from "./QuoteBlock.types";
/** Figma: portrait variants standard | compact | extended; statement = Section/Quote (22137:890679, copy scale 22135:889716 from md). */
/** Figma: portrait variants standard | compact | extended; **`statement`** = Section/Quote (22137890679; **`lg`** single paragraph **2196724638** — About + use cases). */
const QuoteBlockContainer = memo<QuoteBlockProps>(
({
variant: variantProp = "standard",
@@ -19,7 +19,6 @@ const QuoteBlockContainer = memo<QuoteBlockProps>(
fallbackAvatarSrc = "/assets/Quote_Avatar.svg",
onError,
}) => {
const variant = variantProp;
const [imageError, setImageError] = useState(false);
const [imageLoading, setImageLoading] = useState(true);
@@ -86,12 +85,12 @@ const QuoteBlockContainer = memo<QuoteBlockProps>(
},
};
const config = variants[variant] || variants.standard;
const config = variants[variantProp] || variants.standard;
// Use provided ID or generate a stable one based on content
const baseId =
id ||
(variant === "statement"
(variantProp === "statement"
? "statement-quote"
: `quote-${author.toLowerCase().replace(/\s+/g, "-")}`);
const quoteId = `${baseId}-content`;
@@ -124,7 +123,7 @@ const QuoteBlockContainer = memo<QuoteBlockProps>(
};
// Validate required props
if (variant === "statement") {
if (variantProp === "statement") {
if (!quote?.trim() || !quoteSecondary?.trim()) {
logger.error(
"QuoteBlock: statement variant requires non-empty quote and quoteSecondary",
@@ -134,7 +133,7 @@ const QuoteBlockContainer = memo<QuoteBlockProps>(
type: "missing_props",
message:
"QuoteBlock statement variant requires quote and quoteSecondary",
quote: !!quote?.trim() && !!quoteSecondary?.trim(),
quote: !!(quote?.trim() && quoteSecondary?.trim()),
});
}
return null;
@@ -5,13 +5,11 @@ export type QuoteBlockVariantValue =
| "statement";
export interface QuoteBlockProps {
/** Default `standard` (home portrait quote). `statement` is About-only dual-paragraph layout; isolated branch in QuoteBlock.view. */
/** Default `standard` (home portrait quote). **`statement`** = yellow Section / Quote (**About** + **`/use-cases`** — two paragraphs below **`lg`**, one paragraph from **`lg`**; [21967-24638](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21967-24638&m=dev)). */
variant?: QuoteBlockVariantValue;
className?: string;
quote?: string;
/**
* Second paragraph for **`statement`** variant (Figma Section/Quote 22137:890679).
*/
/** Second paragraph for **`statement`** (Section/Quote); merged into one `<p>` from **`lg`**. */
quoteSecondary?: string;
author?: string;
source?: string;
@@ -39,7 +37,7 @@ export interface VariantConfig {
source: string;
showDecor: boolean;
/**
* When true, render Figma **Section/Quote** layout (yellow surface, dual paragraphs, no attribution).
* When true, render Figma **Section/Quote** layout (yellow surface; stacked copy below **`lg`**, single paragraph from **`lg`**; no attribution).
*/
statementLayout?: boolean;
}
@@ -26,15 +26,12 @@ function QuoteBlockView({
const avatarAlt = t("avatarAlt").replace("{author}", author);
if (config.statementLayout) {
if (!quoteSecondary?.trim()) {
return null;
}
const statementTextClass =
"font-bricolage-grotesque text-[28px] font-bold leading-9 tracking-[var(--text-xx-large-heading--letter-spacing)] text-[var(--color-surface-default-tertiary)] md:text-[length:var(--text-xx-large-heading)] md:leading-[length:var(--text-xx-large-heading--line-height)]";
return (
<section
data-figma-node="21967-24638"
className={`${config.container} ${className}`.trim()}
aria-labelledby={quoteId}
role="region"
@@ -43,12 +40,18 @@ function QuoteBlockView({
className="relative box-border flex w-full max-w-[1440px] shrink-0 flex-col items-center justify-center gap-[var(--space-800)] overflow-hidden rounded-[var(--spacing-scale-020)] bg-[var(--color-surface-invert-brand-primary,#fefcc9)] px-[var(--spacing-scale-032)] py-[var(--spacing-scale-048)] md:px-[var(--space-1800)] md:py-[var(--space-2400)]"
>
<QuoteStatementDecor />
<div className="relative z-10 flex w-full min-w-0 shrink-0 flex-col items-center gap-9 text-center md:gap-[length:var(--text-xx-large-heading--line-height)]">
<p id={quoteId} className={`${statementTextClass} mb-0 w-full min-w-0`}>
{quote}
</p>
<p className={`${statementTextClass} mb-0 w-full min-w-0`}>
{quoteSecondary}
<div className="relative z-10 flex w-full min-w-0 shrink-0 flex-col items-center text-center">
<p
id={quoteId}
className={`${statementTextClass} mb-0 flex w-full min-w-0 flex-col gap-9 text-center md:gap-[length:var(--text-xx-large-heading--line-height)] lg:block lg:gap-0`}
>
<span className="block lg:inline">{quote}</span>
{quoteSecondary ? (
<>
<span className="hidden lg:inline">{" "}</span>
<span className="block lg:inline">{quoteSecondary}</span>
</>
) : null}
</p>
</div>
</div>
@@ -3,7 +3,7 @@
import { memo } from "react";
import { getAssetPath, quoteStatementShapePath } from "../../../../lib/assetUtils";
/** Figma: Section / Quote — Shapes (22137:890679). Radial asset + horizontal gradient mask (side lobes only); grain matches QuoteBlock/HeroDecor. Background `cover` so wide banners still fill lateral mask stripes (square sized by panel height misses them when centered). */
/** Figma: Section / Quote — **`shape-qoute.svg`** (22137:890679). */
const EDGE_MASK =
"linear-gradient(to right, #fff 0%, #fff 14%, rgba(255,255,255,0) 30%, rgba(255,255,255,0) 70%, #fff 86%, #fff 100%)";
@@ -2,11 +2,18 @@
import { useState, useEffect, memo, useMemo, useCallback } from "react";
import { useIsMobile } from "../../../hooks";
import { useMessages } from "../../../contexts/MessagesContext";
import { RelatedArticlesView } from "./RelatedArticles.view";
import type { RelatedArticlesProps } from "./RelatedArticles.types";
const RelatedArticlesContainer = memo<RelatedArticlesProps>(
({ relatedPosts, currentPostSlug, slugOrder = [] }) => {
({
relatedPosts,
currentPostSlug,
slugOrder = [],
variant = "default",
}) => {
const messages = useMessages();
// Memoize filtered posts to prevent unnecessary re-computations
const filteredPosts = useMemo(
() => relatedPosts.filter((post) => post.slug !== currentPostSlug),
@@ -95,6 +102,11 @@ const RelatedArticlesContainer = memo<RelatedArticlesProps>(
return () => clearInterval(progressInterval);
}, [currentIndex, filteredPosts.length, isMobile]);
const useCasesHeadingLines =
variant === "useCases"
? messages.pages.useCases.relatedArticles.title
: undefined;
return (
<RelatedArticlesView
filteredPosts={filteredPosts}
@@ -103,6 +115,8 @@ const RelatedArticlesContainer = memo<RelatedArticlesProps>(
transformStyle={transformStyle}
getProgressStyle={getProgressStyle}
onMouseDown={handleMouseDown}
variant={variant}
useCasesHeadingLines={useCasesHeadingLines}
/>
);
},
@@ -1,9 +1,17 @@
import type { BlogPost } from "../../../../lib/content";
export type RelatedArticlesVariant = "default" | "useCases";
export interface RelatedArticlesProps {
relatedPosts: BlogPost[];
currentPostSlug: string;
slugOrder?: string[];
/**
* **`useCases`**: Figma related section — baseline [**22112-872308**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22112-872308&m=dev),
* **`md`** [**22085-863216**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-863216&m=dev),
* **`lg`** [**20711-14231**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=20711-14231&m=dev) (shell + card row gutter / padding).
*/
variant?: RelatedArticlesVariant;
}
export interface RelatedArticlesViewProps {
@@ -13,4 +21,7 @@ export interface RelatedArticlesViewProps {
transformStyle: React.CSSProperties;
getProgressStyle: (_index: number) => React.CSSProperties;
onMouseDown?: (_e: React.MouseEvent<HTMLDivElement>) => void;
variant?: RelatedArticlesVariant;
/** Stacked title lines (`pages.useCases.relatedArticles.title`) when `variant="useCases"`. */
useCasesHeadingLines?: readonly string[];
}
@@ -8,25 +8,66 @@ export function RelatedArticlesView({
transformStyle,
getProgressStyle,
onMouseDown,
variant = "default",
useCasesHeadingLines,
}: RelatedArticlesViewProps) {
if (filteredPosts.length === 0) {
return null;
}
const isUseCases = variant === "useCases";
return (
<section
className="py-[var(--spacing-scale-032)] lg:py-[var(--spacing-scale-064)]"
className={
isUseCases
? "px-[var(--spacing-scale-020)] py-[var(--spacing-scale-032)] md:px-[var(--spacing-scale-024)] md:py-[var(--spacing-scale-048)] lg:px-[var(--spacing-scale-120)] lg:py-[var(--spacing-scale-064)]"
: "py-[var(--spacing-scale-032)] lg:py-[var(--spacing-scale-064)]"
}
data-testid="related-articles"
{...(isUseCases ? { "data-figma-node": "20711-14231" } : {})}
>
<div className="flex flex-col gap-[var(--spacing-scale-032)] lg:gap-[51px]">
<h2 className="text-[32px] lg:text-[44px] leading-[110%] font-medium text-[var(--color-content-inverse-primary)] text-center">
Related Articles
</h2>
<div
className={
isUseCases
? "mx-auto flex w-full max-w-[1440px] flex-col items-center gap-[var(--spacing-scale-024)] md:gap-[var(--spacing-scale-032)]"
: "flex flex-col gap-[var(--spacing-scale-032)] lg:gap-[51px]"
}
>
{isUseCases && useCasesHeadingLines?.length ? (
<h2 className="mx-auto w-full min-w-0 max-w-[693px] text-center font-bricolage-grotesque text-[28px] font-bold leading-9 text-[var(--color-content-default-primary)] md:text-[32px] md:leading-[40px] lg:text-[40px] lg:leading-[52px]">
{/* Baseline 22112-872308: stacked lines; md+ single line; lg 20711-14231: 40/52, max 693px */}
<span className="flex flex-col md:hidden">
{useCasesHeadingLines.map((line, index) => (
<span key={`${index}-${line}`} className="block">
{line}
</span>
))}
</span>
<span className="hidden md:block">
{useCasesHeadingLines.join(" ")}
</span>
</h2>
) : (
<h2 className="text-center text-[32px] font-medium leading-[110%] text-[var(--color-content-inverse-primary)] lg:text-[44px]">
Related Articles
</h2>
)}
{/* Horizontal Articles Row - Carousel on mobile, Scrollable slider on desktop */}
<div className="flex justify-center overflow-hidden">
<div
className={
isUseCases
? "flex w-full max-w-[1440px] justify-center overflow-hidden"
: "flex justify-center overflow-hidden"
}
>
<div
className={`flex gap-0 transition-transform duration-500 ease-in-out ${
isUseCases
? "lg:gap-[var(--spacing-scale-012)] lg:pl-[var(--spacing-scale-024)]"
: ""
} ${
!isMobile
? "overflow-x-auto scrollbar-hide cursor-grab active:cursor-grabbing"
: ""
@@ -1,2 +1,5 @@
export { default } from "./RelatedArticles.container";
export type { RelatedArticlesProps } from "./RelatedArticles.types";
export type {
RelatedArticlesProps,
RelatedArticlesVariant,
} from "./RelatedArticles.types";
@@ -28,7 +28,7 @@ declare global {
}
const RuleStackContainer = memo<RuleStackProps>(
({ className = "", initialGridEntries }) => {
({ className = "", initialGridEntries, translationNamespace, twoColumnsFromMd }) => {
const router = useRouter();
const [gridEntries, setGridEntries] = useState<TemplateGridCardEntry[] | null>(
() => initialGridEntries ?? null,
@@ -103,6 +103,8 @@ const RuleStackContainer = memo<RuleStackProps>(
className={className}
onTemplateClick={handleTemplateClick}
gridEntries={gridEntries}
translationNamespace={translationNamespace ?? "pages.home.ruleStack"}
twoColumnsFromMd={twoColumnsFromMd}
/>
);
},
@@ -7,6 +7,15 @@ export interface RuleStackProps {
* the client skips the `/api/templates` request.
*/
initialGridEntries?: TemplateGridCardEntry[];
/**
* Prefix for `title`, `subtitle`, `button.seeAllTemplates` keys (default
* matches home: `pages.home.ruleStack`).
*/
translationNamespace?: string;
/**
* Use **`md`** (640px) for two template columns — `/use-cases` Rule Stack.
*/
twoColumnsFromMd?: boolean;
}
export interface RuleStackViewProps {
@@ -14,4 +23,6 @@ export interface RuleStackViewProps {
onTemplateClick: (_slug: string) => void;
/** `null` while loading curated templates from the API. */
gridEntries: TemplateGridCardEntry[] | null;
translationNamespace: string;
twoColumnsFromMd?: boolean;
}
@@ -7,16 +7,20 @@ import { GovernanceTemplateGrid } from "../GovernanceTemplateGrid";
import { GovernanceTemplateGridSkeleton } from "../GovernanceTemplateGrid/GovernanceTemplateGridSkeleton";
import type { RuleStackViewProps } from "./RuleStack.types";
/** Figma **Section / RuleStack** [22085:860413](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-860413&m=dev). */
export function RuleStackView({
className,
onTemplateClick,
gridEntries,
translationNamespace,
twoColumnsFromMd = false,
}: RuleStackViewProps) {
const t = useTranslation("pages.home.ruleStack");
const t = useTranslation(translationNamespace);
const buttonText = t("button.seeAllTemplates");
return (
<section
data-figma-node="22085-860413"
className={`
w-full bg-transparent flex flex-col
px-[20px] py-[32px]
@@ -34,14 +38,19 @@ export function RuleStackView({
title={t("title")}
subtitle={t("subtitle")}
variant="multi-line"
ruleStackDesktopTypeScale
/>
{gridEntries === null ? (
<GovernanceTemplateGridSkeleton count={4} />
<GovernanceTemplateGridSkeleton
count={4}
twoColumnsFromMd={twoColumnsFromMd}
/>
) : (
<GovernanceTemplateGrid
entries={gridEntries}
onTemplateClick={onTemplateClick}
twoColumnsFromMd={twoColumnsFromMd}
/>
)}
@@ -0,0 +1,17 @@
"use client";
import { memo } from "react";
import UseCasesOrgsView from "./UseCasesOrgs.view";
import type { UseCasesOrgsProps } from "./UseCasesOrgs.types";
/**
* Figma: **Orgs** instance ([21993-33687](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21993-33687&m=dev)) —
* **305×305** `CaseStudy` tiles, **8px** gap, **24px** horizontal / **48px** bottom inset.
*/
const UseCasesOrgsContainer = memo<UseCasesOrgsProps>((props) => {
return <UseCasesOrgsView {...props} />;
});
UseCasesOrgsContainer.displayName = "UseCasesOrgs";
export default UseCasesOrgsContainer;
@@ -0,0 +1,8 @@
import type { ReactNode } from "react";
export interface UseCasesOrgsProps {
children: ReactNode;
className?: string;
}
export interface UseCasesOrgsViewProps extends UseCasesOrgsProps {}
@@ -0,0 +1,21 @@
"use client";
import { memo } from "react";
import type { UseCasesOrgsViewProps } from "./UseCasesOrgs.types";
function UseCasesOrgsView({ children, className = "" }: UseCasesOrgsViewProps) {
return (
<section
data-figma-node="21993-33687"
className={`bg-[var(--color-surface-default-primary)] px-[var(--spacing-scale-024)] pb-[var(--spacing-scale-048)] ${className}`.trim()}
>
<div className="mx-auto flex w-full max-w-[1440px] flex-wrap content-center items-center justify-center gap-[var(--spacing-scale-008)] lg:flex-nowrap">
{children}
</div>
</section>
);
}
UseCasesOrgsView.displayName = "UseCasesOrgsView";
export default memo(UseCasesOrgsView);
@@ -0,0 +1,2 @@
export { default } from "./UseCasesOrgs.container";
export type { UseCasesOrgsProps } from "./UseCasesOrgs.types";
@@ -110,7 +110,7 @@ const ContentLockupContainer = memo<ContentLockupProps>(
titleGroup: "flex flex-col gap-[var(--spacing-scale-008)]",
titleContainer: "flex gap-[var(--spacing-scale-008)] items-center",
title:
"font-bricolage-grotesque font-medium text-[36px] leading-[110%] tracking-[0] md:text-[52px] md:leading-[110%] text-[var(--color-content-default-brand-primary)]",
"font-bricolage-grotesque font-medium text-[36px] leading-[110%] tracking-[0] lg:text-[44px] lg:leading-[1.1] text-[var(--color-content-default-brand-primary)]",
subtitle:
"font-inter font-normal text-[18px] leading-[130%] tracking-[0] md:text-[24px] md:leading-[32px] text-[var(--color-content-default-primary)]",
shape:
@@ -122,7 +122,7 @@ const ContentLockupContainer = memo<ContentLockupProps>(
titleGroup: "flex flex-col gap-[var(--spacing-scale-008)]",
titleContainer: "flex gap-[var(--spacing-scale-008)] items-center",
title:
"font-bricolage-grotesque font-medium text-[36px] leading-[110%] tracking-[0] md:text-[52px] md:leading-[110%] text-[var(--color-content-inverse-primary)]",
"font-bricolage-grotesque font-medium text-[36px] leading-[110%] tracking-[0] lg:text-[44px] lg:leading-[1.1] text-[var(--color-content-inverse-primary)]",
subtitle:
"font-inter font-normal text-[18px] leading-[130%] tracking-[0] md:text-[24px] md:leading-[32px] text-[var(--color-content-inverse-primary)]",
shape:
@@ -0,0 +1,46 @@
"use client";
import { memo, useId } from "react";
import PageHeaderView from "./PageHeader.view";
import type { PageHeaderProps } from "./PageHeader.types";
/**
* Figma: "Type / PageHeader" (21004-15902).
* Minimal headline-only: Section/PageHeader (22112-871523); md density **22085-862431** when `sectionMinimal` is set;
* Use cases **`lg`** single-line title **21004-24825** when `singleLineTitleFromLg` is set;
* **`xl`** headline scale **22085-860408** when `sectionMinimal` (X Large/Display / `--sizing-1600`).
*/
const PageHeaderContainer = memo<PageHeaderProps>(
({
title,
description,
ctaText,
ctaHref,
headingAlign = "start",
sectionMinimal = false,
singleLineTitleFromLg = false,
titleId: titleIdProp,
className = "",
}) => {
const reactId = useId();
const titleId = titleIdProp ?? `${reactId}-page-header-title`;
return (
<PageHeaderView
title={title}
description={description}
ctaText={ctaText}
ctaHref={ctaHref}
headingAlign={headingAlign}
sectionMinimal={sectionMinimal}
singleLineTitleFromLg={singleLineTitleFromLg}
titleId={titleId}
className={className}
/>
);
},
);
PageHeaderContainer.displayName = "PageHeader";
export default PageHeaderContainer;
@@ -0,0 +1,24 @@
export interface PageHeaderProps {
/** Single line or stacked lines inside one `<h1>` (matches Figma line breaks when centered). */
title: string | readonly string[];
description?: string;
ctaText?: string;
ctaHref?: string;
/** `center` stacks and centers the headline (Section/PageHeader minimal / use cases). */
headingAlign?: "start" | "center";
/**
* Section/PageHeader minimal density ([22085-862431](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-862431&m=dev)):
* md+ **52px** display type and **56px** vertical padding (with **64px** horizontal).
*/
sectionMinimal?: boolean;
/**
* When `title` is multiple lines, use one centered line from **`lg`** ([21004-24825](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21004-24825&m=dev)).
*/
singleLineTitleFromLg?: boolean;
titleId?: string;
className?: string;
}
export type PageHeaderViewProps = Omit<PageHeaderProps, "titleId"> & {
titleId: string;
};
@@ -0,0 +1,106 @@
"use client";
import { Fragment, memo } from "react";
import Button from "../../buttons/Button";
import type { PageHeaderViewProps } from "./PageHeader.types";
function PageHeaderView({
title,
description,
ctaText,
ctaHref,
headingAlign = "start",
sectionMinimal = false,
singleLineTitleFromLg = false,
titleId,
className = "",
}: PageHeaderViewProps) {
const hasCta = Boolean(ctaText?.trim() && ctaHref?.trim());
const hasDescription = Boolean(description?.trim());
const isCenter = headingAlign === "center";
const titleLines = typeof title === "string" ? [title] : title;
const collapseTitleAtLg =
singleLineTitleFromLg && titleLines.length > 1;
const lockupAlign = isCenter
? "items-center text-center"
: "items-start text-[var(--color-content-default-primary)]";
const h1Align = isCenter ? "text-center" : "";
const sectionPadding = sectionMinimal
? "py-[var(--spacing-scale-032)] md:py-[var(--spacing-scale-056)]"
: "py-[var(--spacing-scale-032)] md:py-[var(--spacing-scale-032)]";
const titleTypeClasses = sectionMinimal
? "font-bricolage-grotesque text-[32px] font-medium leading-[1.1] text-[var(--color-content-default-primary)] md:text-[52px] md:leading-[1.1] lg:text-[52px] lg:leading-[1.1] xl:text-[length:var(--sizing-1600)] xl:leading-[1.1]"
: "font-bricolage-grotesque text-[32px] font-medium leading-[1.1] text-[var(--color-content-default-primary)] md:text-[44px] md:leading-[110%] lg:text-[52px]";
const sectionFigmaNode =
sectionMinimal && collapseTitleAtLg
? "21004-24825"
: sectionMinimal
? "22085-862431"
: "21004-22394";
return (
<section
data-figma-node={sectionFigmaNode}
className={`bg-[var(--color-surface-default-primary)] px-[var(--spacing-scale-020)] md:px-[var(--spacing-scale-064)] ${sectionPadding} ${className}`.trim()}
>
<div
className={`mx-auto flex w-full max-w-[1440px] flex-col gap-[var(--spacing-scale-024)] ${isCenter ? "items-center" : ""}`}
>
<div
className={`flex w-full flex-col gap-[var(--spacing-scale-020)] ${lockupAlign}`}
>
<h1
id={titleId}
className={`${titleTypeClasses} ${h1Align}${collapseTitleAtLg ? " lg:whitespace-nowrap" : ""}`.trim()}
>
{titleLines.length === 1 ? (
titleLines[0]
) : collapseTitleAtLg ? (
titleLines.map((line, index) => (
<Fragment key={`${index}-${line}`}>
{index > 0 ? (
<span className="hidden lg:inline">{" "}</span>
) : null}
<span className="block lg:inline">{line}</span>
</Fragment>
))
) : (
titleLines.map((line, index) => (
<span key={`${index}-${line}`} className="block">
{line}
</span>
))
)}
</h1>
{hasDescription ? (
<p className="font-inter text-[18px] font-normal leading-[28px] text-[var(--color-content-default-primary)] lg:text-[24px] lg:leading-[28px]">
{description}
</p>
) : null}
</div>
{hasCta ? (
<div
className={`flex ${isCenter ? "justify-center" : "justify-start"}`}
>
<Button
href={ctaHref}
buttonType="filled"
palette="inverse"
size="large"
>
{ctaText}
</Button>
</div>
) : null}
</div>
</section>
);
}
PageHeaderView.displayName = "PageHeaderView";
export default memo(PageHeaderView);
+2
View File
@@ -0,0 +1,2 @@
export { default } from "./PageHeader.container";
export type { PageHeaderProps } from "./PageHeader.types";
@@ -10,6 +10,11 @@ interface SectionHeaderProps {
variant?: SectionHeaderVariantValue;
/** When set with `variant="multi-line"`, large screens show three title lines (Figma SectionCardSteps). */
stackedDesktopLines?: readonly [string, string, string];
/**
* With `variant="multi-line"`, keep **Rule stack** desktop type: title **32/40** at `lg`, **40/52** at `xl`;
* subtitle **18 / 1.3** at `lg`, **24/32** at `xl`, **left-aligned** in its column from `lg` (Figma **22085:860413**).
*/
ruleStackDesktopTypeScale?: boolean;
}
/**
@@ -23,6 +28,7 @@ const SectionHeader = memo<SectionHeaderProps>(
titleLg,
variant: variantProp = "default",
stackedDesktopLines,
ruleStackDesktopTypeScale = false,
}) => {
const variant = variantProp;
const useStackedDesktop =
@@ -47,7 +53,9 @@ const SectionHeader = memo<SectionHeaderProps>(
<h2
className={
variant === "multi-line"
? "font-bricolage-grotesque font-bold text-[28px] leading-[36px] md:text-[32px] md:leading-[40px] lg:w-[410px] lg:text-left xl:text-[40px] xl:leading-[52px] text-[var(--color-content-default-primary)]"
? ruleStackDesktopTypeScale
? "font-bricolage-grotesque font-bold text-[28px] leading-[36px] md:text-[32px] md:leading-[40px] lg:w-full lg:max-w-none lg:text-left lg:text-[32px] lg:leading-[40px] xl:text-[40px] xl:leading-[52px] text-[var(--color-content-default-primary)]"
: "font-bricolage-grotesque font-bold text-[28px] leading-[36px] md:text-[32px] md:leading-[40px] lg:w-[410px] lg:text-left xl:text-[40px] xl:leading-[52px] text-[var(--color-content-default-primary)]"
: "font-bricolage-grotesque font-bold text-[28px] leading-[36px] sm:text-[32px] sm:leading-[40px] lg:text-[32px] lg:leading-[40px] lg:w-[369px] lg:pr-[var(--spacing-scale-096)] xl:text-[40px] xl:leading-[52px] xl:w-[452px] xl:pr-[var(--spacing-scale-096)] text-[var(--color-content-default-primary)]"
}
>
@@ -68,14 +76,18 @@ const SectionHeader = memo<SectionHeaderProps>(
<div
className={
variant === "multi-line"
? "lg:w-[50%] lg:h-[var(--spacing-scale-120)] lg:flex lg:items-center lg:justify-end lg:ml-[var(--spacing-scale-016)] xl:ml-0 xl:w-[50%] xl:h-[156px] xl:flex xl:items-center xl:justify-end"
? ruleStackDesktopTypeScale
? "lg:w-[50%] lg:h-[var(--spacing-scale-120)] lg:flex lg:items-center lg:justify-start lg:ml-[var(--spacing-scale-016)] xl:ml-0 xl:w-[50%] xl:h-[156px] xl:flex xl:items-center xl:justify-start"
: "lg:w-[50%] lg:h-[var(--spacing-scale-120)] lg:flex lg:items-center lg:justify-end lg:ml-[var(--spacing-scale-016)] xl:ml-0 xl:w-[50%] xl:h-[156px] xl:flex xl:items-center xl:justify-end"
: "lg:w-[928px] lg:h-[var(--spacing-scale-120)] lg:flex lg:items-center lg:justify-end xl:h-[156px] xl:flex xl:items-center xl:justify-end"
}
>
<p
className={
variant === "multi-line"
? "font-inter font-normal text-[14px] leading-[20px] md:text-[18px] md:leading-[130%] xl:text-[24px] xl:leading-[32px] text-[var(--color-content-default-tertiary)] lg:text-right"
? ruleStackDesktopTypeScale
? "font-inter font-normal text-[14px] leading-[20px] md:text-[18px] md:leading-[130%] lg:text-left lg:text-[18px] lg:leading-[130%] text-[var(--color-content-default-tertiary)] xl:text-[24px] xl:leading-[32px]"
: "font-inter font-normal text-[14px] leading-[20px] md:text-[18px] md:leading-[130%] xl:text-[24px] xl:leading-[32px] text-[var(--color-content-default-tertiary)] lg:text-right"
: "font-inter font-normal text-[18px] leading-[130%] sm:text-[18px] sm:leading-[32px] lg:text-[24px] lg:leading-[32px] xl:text-[32px] xl:leading-[40px] xl:text-right text-[#484848] sm:text-[var(--color-content-default-tertiary)] lg:text-[var(--color-content-default-tertiary)] xl:text-[var(--color-content-default-tertiary)] tracking-[0px]"
}
>
@@ -0,0 +1,19 @@
"use client";
import { memo, useId } from "react";
import TripleStepView from "./TripleStep.view";
import type { TripleStepProps } from "./TripleStep.types";
/**
* Figma: **Section / Triple Step** ([22084-859405](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22084-859405&m=dev)); type baseline ([22112-871527](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22112-871527&m=dev)); **md+** two-column + **`triple-step.svg`**.
*/
const TripleStepContainer = memo<TripleStepProps>((props) => {
const reactId = useId();
const headingId = `${reactId}-triple-step-heading`;
return <TripleStepView {...props} headingId={headingId} />;
});
TripleStepContainer.displayName = "TripleStep";
export default TripleStepContainer;
@@ -0,0 +1,16 @@
export interface TripleStepStep {
title: string;
body: string;
}
export interface TripleStepProps {
heading: string;
steps: TripleStepStep[];
ctaText: string;
ctaHref: string;
className?: string;
}
export interface TripleStepViewProps extends TripleStepProps {
headingId: string;
}
@@ -0,0 +1,95 @@
"use client";
import Image from "next/image";
import { memo } from "react";
import { getAssetPath } from "../../../../lib/assetUtils";
import AssetIcon from "../../asset/icon";
import Button from "../../buttons/Button";
import type { TripleStepViewProps } from "./TripleStep.types";
const TRIPLE_STEP_NUMERIC_ICONS = [
"numeric_1_circle",
"numeric_2_circle",
"numeric_3_circle",
] as const;
function TripleStepView({
heading,
steps,
ctaText,
ctaHref,
headingId,
className = "",
}: TripleStepViewProps) {
/** Decorative column art — `public/assets/shapes/triple-step.svg` (288×576 viewBox). */
const shapeSrc = getAssetPath("assets/shapes/triple-step.svg");
return (
<section
data-figma-node="22084-859405"
aria-labelledby={headingId}
className={`bg-transparent p-[var(--spacing-scale-032)] md:px-[var(--spacing-scale-032)] md:py-[var(--spacing-scale-048)] lg:px-[var(--spacing-scale-064)] lg:py-[var(--spacing-scale-048)] ${className}`.trim()}
>
<div className="mx-auto grid w-full min-w-0 max-w-[560px] grid-cols-1 gap-[var(--spacing-scale-032)] md:max-w-[1400px] md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] md:items-stretch md:gap-[var(--spacing-scale-060)] lg:items-center">
<div className="flex w-full min-w-0 flex-col gap-[var(--spacing-scale-040)] break-words md:self-start lg:self-center">
<h2
id={headingId}
className="font-bricolage-grotesque text-[length:var(--text-medium-heading)] font-bold leading-[length:var(--text-medium-heading--line-height)] text-[var(--color-content-default-primary)] md:text-[length:var(--text-large-heading)] md:leading-[length:var(--text-large-heading--line-height)]"
>
{heading}
</h2>
{steps.map((step, index) => (
<div
key={step.title}
className="flex flex-col items-start gap-[var(--spacing-scale-016)]"
>
<AssetIcon
name={
TRIPLE_STEP_NUMERIC_ICONS[Math.min(index, 2)]
}
size={32}
className="shrink-0"
/>
<div className="flex min-w-0 flex-col gap-1 text-[var(--color-content-default-primary)]">
<p className="font-bricolage-grotesque text-[18px] font-medium leading-[22px]">
{step.title}
</p>
<p className="font-inter text-[length:var(--text-small-paragraph)] font-normal leading-[length:var(--text-small-paragraph--line-height)]">
{step.body}
</p>
</div>
</div>
))}
<div className="flex justify-start">
<Button
href={ctaHref}
buttonType="outline"
palette="default"
size="medium"
className="max-md:!px-[var(--spacing-scale-012)] max-md:!py-[var(--spacing-scale-010)] max-md:!text-[14px] max-md:!leading-[18px] md:!p-[var(--spacing-scale-012)] md:!text-[16px] md:!leading-5 md:!gap-[var(--spacing-scale-006)]"
>
{ctaText}
</Button>
</div>
</div>
<div
className="hidden min-h-0 min-w-0 w-full md:flex md:items-center md:justify-center"
aria-hidden
>
<Image
src={shapeSrc}
alt=""
width={288}
height={576}
unoptimized
className="pointer-events-none h-auto w-full max-w-full min-w-0 select-none object-contain"
/>
</div>
</div>
</section>
);
}
TripleStepView.displayName = "TripleStepView";
export default memo(TripleStepView);
+2
View File
@@ -0,0 +1,2 @@
export { default } from "./TripleStep.container";
export type { TripleStepProps, TripleStepStep } from "./TripleStep.types";
@@ -5,7 +5,8 @@ import TripleTextBlockView from "./TripleTextBlock.view";
import type { TripleTextBlockProps } from "./TripleTextBlock.types";
/**
* Figma: "Type / TripleTextBlock" stacked 22137:890676; lg 22128:888715; xl 22135:889705.
* Figma: "Type / TripleTextBlock" — use cases **`lg` 22037-26994**, **`xl` 22085-860414**;
* **`md` 22085-862437**; stacked 22137:890676; lg 22128:888715; xl 22135:889705 (default).
*/
const TripleTextBlockContainer = memo<TripleTextBlockProps>((props) => {
const headingId = useId();
@@ -1,6 +1,8 @@
export interface TripleTextBlockColumn {
title: string;
description: string;
/** Optional second paragraph under `description` (e.g. use cases baseline multi-paragraph lockup). */
descriptionSecondary?: string;
/**
* lg+ three-column layout (Figma 22128:888715). When either `lgTitle` or `lgDescription`
* is set, stacked breakpoints use `title`/`description` and lg uses these (missing side falls back).
@@ -16,6 +18,12 @@ export interface TripleTextBlockProps {
ctaText?: string;
ctaHref?: string;
className?: string;
/**
* `useCases`: Figma use cases TripleText **`lg`** ([22037-26994](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22037-26994&m=dev));
* **`xl`** ([22085-860414](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-860414&m=dev));
* `md` ([22085-862437](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-862437&m=dev)); lg 3-col **22128-888715**.
*/
layoutPreset?: "default" | "useCases";
}
export interface TripleTextBlockViewProps extends TripleTextBlockProps {
@@ -12,11 +12,35 @@ function columnUsesLargeBreakpointCopy(column: TripleTextBlockColumn): boolean {
return column.lgTitle !== undefined || column.lgDescription !== undefined;
}
function TripleTextUseCasesColumn({ column }: { column: TripleTextBlockColumn }) {
return (
<div className="flex w-full flex-col gap-[var(--spacing-scale-020)] lg:gap-0 xl:gap-[var(--spacing-scale-020)]">
<div className="flex flex-col gap-[var(--spacing-scale-008)] lg:gap-[var(--spacing-scale-004)] xl:gap-[var(--spacing-scale-008)]">
<h3 className="text-left font-bricolage-grotesque text-[24px] font-medium leading-8 text-[var(--color-content-default-primary,white)] md:text-[32px] md:leading-[1.1] lg:text-[18px] lg:leading-[var(--spacing-scale-022)] xl:text-[32px] xl:leading-[1.1]">
{column.title}
</h3>
<div className="flex flex-col gap-[var(--spacing-scale-024)] font-inter text-[16px] font-normal leading-6 text-[var(--color-content-default-secondary)] md:text-[24px] md:leading-8 lg:gap-[var(--spacing-scale-020)] lg:text-[14px] lg:leading-5 xl:gap-[var(--spacing-scale-032)] xl:text-[24px] xl:leading-8">
<p>{column.description}</p>
{column.descriptionSecondary ? (
<p>{column.descriptionSecondary}</p>
) : null}
</div>
</div>
</div>
);
}
function TripleTextBlockColumnLockup({
column,
layoutPreset,
}: {
column: TripleTextBlockColumn;
layoutPreset: "default" | "useCases";
}) {
if (layoutPreset === "useCases") {
return <TripleTextUseCasesColumn column={column} />;
}
const dual = columnUsesLargeBreakpointCopy(column);
const lgSubtitle = column.lgTitle ?? column.title;
const lgBody = column.lgDescription ?? column.description;
@@ -55,7 +79,11 @@ function TripleTextBlockColumnLockup({
}
/**
* Figma: "Type / TripleTextBlock" stacked **22137:890676**; lg 3-col **22128-888715**; xl typography + horizontal inset scale/160 **22135:889705** (Subtitle 32 Small/Display, Body X Large/Paragraph 24 / lh 32; section px scale/160, py scale/064).
* Section horizontal padding adds **+ Scale/096** below `xl` (outer frame inset); **use cases `xl`** uses **Scale/160** only ([22085:860414](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-860414&m=dev)).
*
* Figma: use cases **`lg`** [22037:26994](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22037-26994&m=dev);
* **`md`** [22085:862437](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-862437&m=dev); stacked **22137:890676**;
* lg 3-col **22128:888715**; xl **22135:889705** (default preset).
*/
function TripleTextBlockView({
title = "",
@@ -64,39 +92,71 @@ function TripleTextBlockView({
ctaHref,
headingId,
className = "",
layoutPreset = "default",
}: TripleTextBlockViewProps) {
const sectionTitle = title.trim();
const hasSectionTitle = sectionTitle.length > 0;
const isUseCases = layoutPreset === "useCases";
return (
<section
{...(isUseCases ? { "data-figma-node": "22085-860414" } : {})}
aria-labelledby={hasSectionTitle ? headingId : undefined}
className={`bg-black px-[var(--spacing-scale-032)] py-[var(--spacing-scale-064)] md:px-[var(--spacing-scale-096)] md:py-[var(--spacing-scale-064)] xl:px-[var(--spacing-scale-160)] ${className}`.trim()}
className={`bg-black px-[calc(var(--spacing-scale-032)+var(--spacing-scale-096))] py-[var(--spacing-scale-064)] md:px-[calc(var(--spacing-scale-096)+var(--spacing-scale-096))] md:py-[var(--spacing-scale-064)] lg:px-[calc(var(--spacing-scale-096)+var(--spacing-scale-096))] lg:py-[var(--spacing-scale-064)] ${
isUseCases
? "xl:px-[var(--spacing-scale-160)]"
: "xl:px-[calc(var(--spacing-scale-160)+var(--spacing-scale-096))]"
} xl:py-[var(--spacing-scale-064)] ${className}`.trim()}
>
<div className="mx-auto flex w-full max-w-[1440px] flex-col items-start gap-[var(--spacing-scale-032)]">
<div
className={
isUseCases
? "mx-auto flex w-full max-w-[1440px] flex-col items-start gap-[var(--spacing-scale-032)] md:gap-[var(--spacing-scale-048)] lg:items-center lg:gap-[var(--spacing-scale-064)]"
: "mx-auto flex w-full max-w-[1440px] flex-col items-start gap-[var(--spacing-scale-032)]"
}
>
{hasSectionTitle ? (
<h2
id={headingId}
className="w-full text-left font-bricolage-grotesque text-[32px] font-medium leading-[1.1] text-[var(--color-content-default-primary,white)]"
className={
isUseCases
? "w-full text-left font-bricolage-grotesque text-[28px] font-bold leading-9 text-[var(--color-content-default-primary,white)] md:text-[32px] md:leading-[40px] lg:mx-auto lg:max-w-[693px] lg:text-center lg:text-[36px] lg:font-extrabold lg:leading-[44px] xl:text-[40px] xl:leading-[52px] xl:font-bold"
: "w-full text-left font-bricolage-grotesque text-[32px] font-medium leading-[1.1] text-[var(--color-content-default-primary,white)]"
}
>
{sectionTitle}
</h2>
) : null}
<div className="flex w-full flex-col gap-[var(--spacing-scale-032)] lg:flex-row lg:items-start lg:gap-[var(--spacing-scale-032)]">
<div
className={
isUseCases
? "flex w-full flex-col gap-[var(--spacing-scale-048)] lg:flex-row lg:items-start lg:gap-[var(--spacing-scale-032)]"
: "flex w-full flex-col gap-[var(--spacing-scale-032)] lg:flex-row lg:items-start lg:gap-[var(--spacing-scale-032)]"
}
>
{columns.map((column, index) => (
<div
key={`${column.title}-${column.lgTitle ?? ""}-${index}`}
className="w-full min-w-0 lg:flex-1"
>
<TripleTextBlockColumnLockup column={column} />
<TripleTextBlockColumnLockup
column={column}
layoutPreset={layoutPreset}
/>
</div>
))}
</div>
{ctaText ? (
<div className="flex w-full justify-start">
<div
className={
isUseCases
? "flex w-full justify-start lg:justify-center"
: "flex w-full justify-start"
}
>
<Button
buttonType="filled"
palette="inverse"
buttonType={isUseCases ? "outline" : "filled"}
palette={isUseCases ? "default" : "inverse"}
size="large"
href={ctaHref}
>
+7 -2
View File
@@ -7,7 +7,7 @@ Quick map from the Figma file **Community Rule System** (`agv0VBLiBlcnSAaiAORgPR
| [Utility](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=20515-15809) | `utility/` | Create chrome, tag, scroll, sidebar, dividers, etc. |
| [Asset](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=1240-9089) | **`app/components/asset/`**, **`public/assets/template-mark/`**, **`public/assets/vector/`** | Components under **`asset/`**; flat kebab **`*.svg`** in **`template-mark/`** & **`vector/`** (see conventions below). |
| [Button](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=497-3016) | `buttons/` | PascalCase package per primitive — **`Button/`**, **`InlineTextButton/`** (see conventions below). |
| [Card](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=17865-24349) | `cards/` | One PascalCase package per surface—**`Selection/`** (Figma **Card / CardSelection**), **`CardStack/`**, **`Rule/`** (Figma **Card / Rule**), **`Icon/`**, **`Mini/`**, **`Stat/`** (Figma **Card / Stat**), **`Step/`** (Figma **Card / Step**), **`TemplateReviewCard/`** (see conventions below). |
| [Card](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=17865-24349) | `cards/` | One PascalCase package per surface—**`Selection/`** (Figma **Card / CardSelection**), **`CardStack/`**, **`Rule/`** (Figma **Card / Rule**), **`Icon/`**, **`CaseStudy/`**, **`Mini/`**, **`Stat/`** (Figma **Card / Stat**), **`Step/`** (Figma **Card / Step**), **`TemplateReviewCard/`** (see conventions below). |
| [Control](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=5944-58611) | `controls/` | Checkbox, radio, text field, select, toggle, switch, incrementer, upload, multi-select, chip, … (see **Control conventions** below). **`InfoMessageBox`** canonical here. |
| [Layout](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21836-20542) | `layout/` | **`List/`**, **`ListEntry/`**, **`ListItem/`** + **`listSizeLayout.ts`**. **Tabs** / **Accordion** are in Figma only—**not** in code yet (see **Layout conventions**). |
| [Modals](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=5944-47704) | `modals/` | Alert, Create, Dialog, Login, Tooltip, **`ModalHeader`** / **`ModalFooter`** (see **Modals conventions**). |
@@ -151,9 +151,11 @@ Inventory aligns with [**CR-104**](https://linear.app/community-rule/issue/CR-10
| FAQ accordion | **`Accordion/`** | Section wrapper over **`layout/Accordion`**; **`/about`**. |
| Related content | **`RelatedArticles/`** | Article list / cards — confirm naming vs Figma “related slider” frames. |
| Template grid (governance) | **`GovernanceTemplateGrid/`** | **`GovernanceTemplateGridSkeleton`** colocated. |
| Use cases org strip | **`UseCasesOrgs/`** | Figma [**22112-873893**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22112-873893); composes **`cards/CaseStudy`**; **`/use-cases`**. |
| “Who is this for?” icon grid | **`Groups/`** | Figma [**22084-859470**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22084-859470); composes **`cards/Icon`** (`interactive={false}`); **`/use-cases`**. |
| Section index / number | **`SectionNumber.tsx`** | Single module. |
**Gaps / TBD (§10, confirm with design / roadmap):** **PageHeader**, **CardGroup**, **Related slider** (vs **`RelatedArticles`** parity), **orgs** strip, and other Figma-only compositions.
**Gaps / TBD (§10, confirm with design / roadmap):** **CardGroup**, **Related slider** (vs **`RelatedArticles`** parity), and other Figma-only compositions not yet mapped to a shipped route.
- **Pattern:** Prefer **`container` / `view` / `types`** + **`index.tsx`** for **new** section composites. Older or small surfaces may stay a **single `*.tsx`** at **`sections/`** root (**`ContentBanner`**, **`SectionNumber`**) — match neighbors when extending.
@@ -168,7 +170,9 @@ Inventory aligns with [**CR-104**](https://linear.app/community-rule/issue/CR-10
| Section header (1 vs 3 lines, responsive sizes) | **`SectionHeader/`** | Figma [**17411:10981**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=17411-10981). **`SectionHeader.tsx`** + **`index.tsx`**; **`default`** / **`multi-line`**; optional **`stackedDesktopLines`**. Composed by **`sections/CardSteps`**, **`sections/RuleStack`**, etc. |
| Header lockup / content lockup | **`HeaderLockup/`**, **`ContentLockup/`** | **`container` / `view` / `types`** + **`index.tsx`** where split. **`ContentLockup`** **`about`** variant + optional **`titleContent`** for **`AboutHeader`**. |
| About header (inline word + shape lockup) | **`AboutHeader/`** | **`/about`** hero; composes **`ContentLockup`**. |
| Marketing page header (title + body + optional CTA) | **`PageHeader/`** | Figma [**21004-15902**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21004-15902); **`/use-cases`**. |
| Triple text block | **`TripleTextBlock/`** | **`/about`** (columns only; optional title/CTA omitted per page). |
| Three-step explainer + CTA | **`TripleStep/`** | Figma [**22084-859256**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22084-859256); **`/use-cases`** (illustration column deferred). |
| Type / Numbered List (+ item) | **`NumberedList/`** | **`container` / `view` / `types`** + **`index.tsx`**. |
| `.utility/Input label` (often filed under Utility in Figma) | **`InputLabel/`** | See also **Utility conventions****`InputLabel`** is canonical under **`type/`**. |
| “Community Rule” published body (Sections canvas) | **`CommunityRule/`** | Composes **`Section`** + **`TextBlock`**. Category + entries; optional entry **`blocks`**; plain **`body`** splits on blank lines. |
@@ -185,6 +189,7 @@ Inventory aligns with [**CR-104**](https://linear.app/community-rule/issue/CR-10
- **`Stat/`** — Figma **Card / Stat** (metric tile + decorative shape). Composed by **`sections/Stats`** on **`/about`**.
- **`Step/`** — Figma **Card / Step** (numbered step tile + text). Shipped on the home page via **`sections/CardSteps`**. Not the **Progress / Stepper** wizard control.
- **`CardStack/`** — selectable stacks + expand affordance for create-flow method pickers (**Figma may still say “Utility / CardStack”;** code lives here).
- **`CaseStudy/`** — Figma **Card / CaseStudy** ([21993-32352](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21993-32352)). Square org tile with **`lavender` \| `neutral` \| `rose`** surfaces; optional **`visual`** slot. Composed by **`sections/UseCasesOrgs`** on **`/use-cases`**.
- **`TemplateReviewCard/`** — template review grid + chip detail modal (**`TemplateChipDetailModal`** colocated in the package).
## Asset conventions (Figma “Asset” canvas)
+2
View File
@@ -16,6 +16,7 @@ import home from "./pages/home.json";
import templates from "./pages/templates.json";
import learn from "./pages/learn.json";
import about from "./pages/about.json";
import useCases from "./pages/useCases.json";
import monitor from "./pages/monitor.json";
import login from "./pages/login.json";
import profile from "./pages/profile.json";
@@ -78,6 +79,7 @@ export default {
templates,
learn,
about,
useCases,
monitor,
login,
profile,
+10
View File
@@ -9,5 +9,15 @@
"decision-making",
"operating manual"
]
},
"useCases": {
"title": "Use cases — CommunityRule",
"description": "See how mutual aid groups, cooperatives, open source projects, and other communities use CommunityRule to define structure before they need it.",
"keywords": [
"use cases",
"mutual aid",
"community governance",
"operating manual"
]
}
}
+90
View File
@@ -0,0 +1,90 @@
{
"pageHeader": {
"title": ["See how groups use", "CommunityRule"]
},
"caseStudyTiles": {
"mutualAidColoradoAlt": "Mutual Aid Colorado logo",
"foodNotBombsAlt": "Food Not Bombs logo",
"boulderCountyStreetMedicsAlt": "Boulder County Street Medics logo"
},
"quote": {
"paragraph1": "Most communities don't fail because of a lack of passion, they fail because of undefined processes.",
"paragraph2": "CommunityRule helps by providing a modular library of open-source operating manuals that groups can fork, customize, and export to make their governance explicit."
},
"groups": {
"title": "Who is this for?",
"items": [
{
"title": "Worker's cooperatives",
"description": "Employee-owned businesses often need to clarify how power is shared, decisions are made, and how processes operate within their organizations."
},
{
"title": "Mutual aid groups",
"description": "Mutual aid groups must define how resources are shared, decisions are made, and volunteer coordination operates within their organizations."
},
{
"title": "Open source projects",
"description": "Agree to how contributions are managed, technical decisions are made, and community participation operates within their development communities."
},
{
"title": "DAOs",
"description": "Document token-based voting process, proposal patterns, and how community governance operates within their blockchain ecosystems."
}
]
},
"tripleStep": {
"heading": "Get recommendations that will make organizing easier",
"ctaText": "Create Rule",
"ctaHref": "/create",
"steps": [
{
"title": "Get your stakeholders together",
"body": "If you're just getting started, you might begin with shared values, decision-making plan, and conflict resolution process—keeping it simple ensures guidelines remain practical rather than overwhelming."
},
{
"title": "Define how your group should operate",
"body": "Involving everyone in shaping these agreements through group discussions, workshops, or a tool like CommunityRule can help build collective buy-in."
},
{
"title": "Have a durable impact",
"body": "Consider treating guidelines as a living document that evolves with your group's needs. A little structure early on helps keep your group effective and sustainable. CommunityRule makes it easy to track and share these changes as your group evolves."
}
]
},
"tripleTextBlock": {
"title": "Why Horizontal groups need CommunityRule",
"ctaText": "Setup your community",
"ctaHref": "/create",
"columns": [
{
"title": "Share Leadership and Prevent Burnout",
"description": "Rotating roles and setting clear expectations for contributions can prevent a few people from taking on too much. A living document outlining responsibilities and group values helps distribute labor fairly and avoids misunderstandings.",
"descriptionSecondary": "A transportation assistance group avoided burnout by introducing a weekly shift system, making participation more sustainable."
},
{
"title": "Establish Transparent, Inclusive Decision-Making",
"description": "Figuring out how your team makes decisions right from the start saves a lot of headaches. You can use full consensus, majority rules, or let specific people make the call. Being open about this and checking in often keeps everyone involved and prevents secret power struggles. As an example, one disaster relief group sped things up by using a \"consensus-minus-one\" rule. It kept them aligned but stopped a single person from bringing everything to a halt."
},
{
"title": "Empower Your Community with Transparent Governance",
"description": "A basic conflict resolution process can prevent disputes from escalating. Clear guidelines for resource distribution reduce misunderstandings and maintain trust. Assume good faith and use structured discussions to resolve issues.",
"descriptionSecondary": "Members noticed frustration when some folks felt unheard in decisions about new projects. By anonymous feedback box, they created a space to address concerns early and make adjustments before conflicts escalated."
}
]
},
"ruleStack": {
"title": "Get Templates that help your community run smoothly",
"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.",
"button": {
"seeAllTemplates": "See all templates"
}
},
"askOrganizer": {
"title": "Still have questions?",
"subtitle": "Get answers from an experienced organizer",
"buttonText": "Ask an organizer"
},
"relatedArticles": {
"title": ["Tools to set your", "group up for", "success"]
}
}
@@ -0,0 +1,75 @@
<svg width="255" height="211" viewBox="0 0 255 211" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M70.3475 169.803L73.6885 175.422C70.0903 176.242 64.5781 178.388 57.7256 182.085L46.0262 188.413L40.7689 194.054L37.1794 188.017L57.6401 166.297L29.4481 175.015L25.8587 168.978C27.007 169.112 28.5633 169.193 30.2224 169.023L49.4697 156.573C54.4208 153.377 56.5418 151.236 58.1432 149.278L63.0304 157.498C60.4074 157.737 56.518 159.547 52.7002 162.006L39.491 170.552C43.7368 169.976 48.4118 168.641 50.1187 168.129L59.4511 165.22L65.4879 161.63L67.7244 165.392L47.2636 187.112L55.7966 182.541C65.6575 177.243 69.1251 171.976 70.3475 169.803Z" fill="black"/>
<path d="M24.1302 163.496L21.8417 153.767C23.3914 154.457 26.2065 154.627 30.3085 153.663L45.7174 150.038C54.7628 147.91 57.6802 145.448 56.6905 141.241C55.4535 135.982 48.422 135.582 40.3758 137.475L32.5924 139.306C24.3884 141.236 21.0936 144.453 20.0974 146.352L18.2913 138.674C19.9679 139.667 24.2647 140.71 32.4687 138.78L40.2521 136.949C47.5095 135.242 56.5641 134.333 58.3702 142.011C59.4836 146.744 58.7345 153.47 44.9033 156.723L31.7558 159.816C27.6538 160.78 25.2099 162.188 24.1302 163.496Z" fill="black"/>
<path d="M25.3755 134.259L25.3739 134.313L18.2137 133.348L18.8098 112.881L26.0142 112.334L26.0126 112.388C22.5507 114.341 19.6398 115.932 19.3147 117.814C19.1165 119.052 19.5758 119.984 21.8439 120.05L48.3051 120.821C52.3552 120.939 55.079 120.207 56.4573 119.275L56.1695 129.157C54.8493 128.092 52.1712 127.257 48.121 127.139L21.6599 126.368C19.4458 126.304 18.879 127.206 19.0064 128.399C19.2643 130.676 22.7766 132.562 25.3755 134.259Z" fill="black"/>
<path d="M19.4205 107.7L22.2561 98.1155C23.26 99.483 25.6204 101.027 29.6612 102.222L44.8401 106.713C53.7506 109.35 57.5048 108.657 58.7311 104.513C60.2639 99.3325 54.3553 95.4997 46.4291 93.1546L38.762 90.886C30.6803 88.4949 26.2241 89.6554 24.4174 90.811L26.6552 83.2474C27.619 84.9411 30.8336 87.9768 38.9152 90.368L46.5824 92.6365C53.7315 94.7518 62.0458 98.4512 59.8079 106.015C58.4284 110.677 54.4439 116.147 40.8191 112.115L27.8678 108.283C23.827 107.088 21.0068 107.098 19.4205 107.7Z" fill="black"/>
<path d="M68.3014 87.1396C65.6384 85.4882 64.3418 84.8113 57.3241 82.4938L50.7766 80.3407L48.6413 83.7842C50.869 85.42 53.6128 87.2486 57.1941 89.4694C59.9489 91.1777 63.3447 92.7749 64.4862 93.292L60.6994 99.3986C59.008 97.2055 56.7418 94.4016 54.3746 92.1708C47.0827 85.1699 42.5283 79.8028 40.1813 76.8217L32.9728 72.3517L35.2506 68.6786L57.2769 75.599C65.4121 78.1645 69.5959 79.1062 73.1131 79.3801L68.3014 87.1396ZM41.0298 77.0936C42.881 79.1315 44.9379 81.0427 48.1821 83.4995L50.2606 80.1478L41.0298 77.0936Z" fill="black"/>
<path d="M84.6502 69.1657L70.4657 83.1734C70.2475 81.4148 68.8876 78.9613 66.0025 76.0398L51.0078 60.8559C48.0468 57.8575 45.5721 56.5049 43.8927 56.2652L51.0042 49.2423C51.2228 50.9246 52.5443 53.416 55.5053 56.4144L75.8906 77.0569C78.5473 73.7499 80.153 67.4566 79.6787 63.9008L84.6502 69.1657Z" fill="black"/>
<path d="M102.491 60.3576C101.294 57.4616 100.637 56.1548 96.2954 50.1743L92.239 44.602L88.4943 46.1495C89.3468 48.7784 90.5056 51.8654 92.1149 55.76C93.3529 58.7558 95.176 62.0359 95.8004 63.1224L89.1597 65.8666C89.068 63.0985 88.8666 59.499 88.2484 56.3055C86.4199 46.3636 85.8586 39.347 85.6952 35.5564L82.4558 27.7174L86.4502 26.0668L100.285 44.5507C105.389 51.3854 108.235 54.5937 110.929 56.8706L102.491 60.3576ZM86.225 36.2728C86.537 39.0082 87.09 41.761 88.2879 45.6501L91.9328 44.1439L86.225 36.2728Z" fill="black"/>
<path d="M117.664 56.4851L107.879 58.5209C108.609 56.9895 108.852 54.1798 107.994 50.0541L103.658 29.2144C102.8 25.0888 101.456 22.6092 100.176 21.4959L109.961 19.4602C109.231 20.9916 108.988 23.8013 109.847 27.9269L114.182 48.7667C115.041 52.8923 116.384 55.3719 117.664 56.4851Z" fill="black"/>
<path d="M113.826 56.1897C114.843 54.8317 115.633 52.1246 115.603 47.9108L115.448 26.6254C115.418 22.4115 114.588 19.7162 113.552 18.373L123.6 18.3001C132.838 18.233 139.268 25.8041 139.35 37.0411C139.432 48.3861 133.113 56.0497 123.875 56.1168L113.826 56.1897ZM123.167 55.3656C128.515 55.3267 132.88 46.2726 132.813 37.0885C132.748 28.1206 128.252 19.0227 122.903 19.0615L121.715 19.0702L121.978 55.3742L123.167 55.3656Z" fill="black"/>
<path d="M151.82 39.4645C156.304 28.6911 163.991 24.2833 169.698 26.6003C172.441 27.7422 175.164 29.6363 176.133 32.0879L171.233 43.8587L171.133 43.8172C172.797 36.7269 171.868 28.5569 167.508 26.8005C164.195 25.4801 160.918 30.2604 158.364 37.0976C154.445 47.638 155.832 56.1153 160.819 58.1915C161.917 58.6483 165.349 59.258 168.727 54.5192L168.835 54.6813C164.756 62.2296 160.606 64.8907 155.381 62.5403C149.807 60.0446 147.397 50.0882 151.82 39.4645Z" fill="black"/>
<path d="M172.241 47.0875C177.672 39.8834 186.609 35.3896 192.001 39.4551C197.824 43.846 196.187 54.7225 189.552 63.5227C184.12 70.7267 175.184 75.2206 169.792 71.155C163.968 66.7642 165.606 55.8876 172.241 47.0875ZM181.714 43.6755C175.144 52.3893 169.106 69.0139 172.6 71.6483C174.282 72.9168 177.422 70.5476 180.121 66.9672C186.691 58.2533 192.73 41.6288 189.236 38.9943C187.553 37.7259 184.414 40.095 181.714 43.6755Z" fill="black"/>
<path d="M188.241 90.3261L175.724 74.8103C177.495 74.7693 180.073 73.6619 183.268 71.0839L199.877 57.6849C203.157 55.039 204.751 52.7122 205.157 51.0652L211.433 58.8441C209.737 58.8933 207.126 59.9586 203.846 62.6045L181.266 80.8204C184.291 83.7949 190.392 86.0227 193.977 85.9068L188.241 90.3261Z" fill="black"/>
<path d="M204.288 77.6962C212.462 73.8754 222.462 74.0908 225.322 80.2085C228.41 86.8156 221.869 95.6583 211.885 100.326C203.712 104.146 193.711 103.931 190.851 97.8133C187.763 91.2061 194.304 82.3635 204.288 77.6962ZM214.257 79.1191C204.371 83.7407 191.248 95.6002 193.101 99.5644C193.994 101.473 197.877 100.85 201.939 98.9515C211.826 94.33 224.948 82.4705 223.095 78.5062C222.202 76.5975 218.319 77.2202 214.257 79.1191Z" fill="black"/>
<path d="M196.141 112.92L194.311 102.82C195.823 103.589 198.625 103.905 202.772 103.153L223.717 99.3577C227.863 98.6063 230.376 97.3272 231.522 96.0763L233.623 107.665C234.49 112.449 233.772 116.368 228.935 117.245C224.948 117.967 222.76 115.289 220.017 112.876L210.533 118.109C206.488 120.324 201.688 123.226 198.424 125.519L196.796 116.535C199.029 116.131 204.592 113.805 208.188 111.835L213.133 109.127C210.969 108.696 208.412 108.83 205.861 109.292C202.299 109.938 198.756 111.293 196.141 112.92ZM198.895 105.393C204.993 105.716 212.114 108.049 215.366 109.327C219.448 110.948 224.724 111.584 228.445 110.909C231.422 110.37 233.385 109.081 232.961 106.742C232.691 105.253 231.223 104.421 227.512 104.545C218.011 104.839 208.352 105.162 198.895 105.393Z" fill="black"/>
<path d="M197.107 134.347C200.232 134.575 201.694 134.573 208.986 133.371L215.786 132.242L216.08 128.2C213.348 127.785 210.069 127.437 205.866 127.131C202.633 126.896 198.884 127.056 197.633 127.127L198.155 119.961C200.67 121.119 203.979 122.552 207.111 123.43C216.818 126.25 223.342 128.892 226.804 130.444L235.264 131.061L234.95 135.371L212.226 139.458C203.829 140.958 199.686 142.065 196.443 143.454L197.107 134.347ZM225.927 130.597C223.341 129.65 220.633 128.911 216.619 128.24L216.332 132.173L225.927 130.597Z" fill="black"/>
<path d="M197.606 138.606C198.533 140.027 200.805 141.698 204.774 143.114L224.821 150.268C228.79 151.684 231.607 151.829 233.224 151.316L229.846 160.78C226.742 169.481 217.425 172.925 206.841 169.149C196.156 165.336 191.124 156.771 194.229 148.07L197.606 138.606ZM195.177 147.663C193.379 152.7 200.388 159.905 209.038 162.992C217.485 166.006 227.573 164.902 229.37 159.865L229.77 158.745L195.576 146.544L195.177 147.663Z" fill="black"/>
<path d="M207.91 169.513C215.276 174.723 220.04 183.519 216.14 189.032C211.929 194.987 201.007 193.681 192.009 187.316C184.643 182.106 179.88 173.311 183.78 167.797C187.991 161.843 198.913 163.149 207.91 169.513ZM211.609 178.879C202.699 172.577 185.899 167.047 183.372 170.619C182.155 172.339 184.619 175.405 188.28 177.995C197.189 184.297 213.99 189.827 216.517 186.255C217.733 184.534 215.27 181.468 211.609 178.879Z" fill="black"/>
<path d="M150.237 152.316L151.891 152.821C153.549 153.317 155.217 153.78 156.893 154.207C164.792 156.22 172.882 157.457 181.021 157.894C181.225 157.905 181.319 158.075 181.319 158.213C181.319 158.283 181.298 158.361 181.242 158.423C181.197 158.473 181.138 158.504 181.071 158.515L181.003 158.518C178.693 158.394 176.387 158.209 174.088 157.957C165.937 157.065 157.883 155.367 150.059 152.915C149.863 152.854 149.817 152.663 149.853 152.529C149.872 152.462 149.913 152.393 149.982 152.348C150.038 152.312 150.103 152.296 150.17 152.302L150.237 152.316Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M98.336 131.94C101.13 131.521 103.978 131.743 106.713 132.374C112.434 133.692 117.51 136.745 122.467 139.699C127.654 142.79 132.969 145.602 138.519 147.981C138.605 148.018 138.669 148.089 138.69 148.179C138.709 148.263 138.687 148.342 138.652 148.401C138.584 148.517 138.422 148.618 138.241 148.541C137.564 148.25 136.89 147.955 136.218 147.653C133.476 146.422 130.778 145.092 128.132 143.665C125.524 142.258 122.989 140.729 120.446 139.223C115.465 136.274 110.276 133.439 104.507 132.59C101.79 132.191 99.0174 132.276 96.3663 132.992C94.2951 133.551 92.3318 134.453 90.4708 135.529C86.6521 137.735 83.2918 140.591 79.67 143.143C76.209 145.581 72.4311 147.77 68.1592 148.447C67.9637 148.478 67.8322 148.339 67.796 148.21C67.7775 148.143 67.7769 148.061 67.8165 147.985C67.8592 147.903 67.9375 147.851 68.0303 147.837L68.4307 147.769C68.8296 147.698 69.226 147.614 69.6192 147.516C73.8425 146.467 77.523 143.975 81.0098 141.406C84.5639 138.787 88.0546 135.928 92.0723 133.965C94.0477 133 96.1496 132.268 98.336 131.94Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M157.089 156.849C164.382 158.688 171.829 159.877 179.334 160.384C179.536 160.397 179.629 160.568 179.629 160.705C179.629 160.775 179.608 160.853 179.551 160.915C179.506 160.965 179.446 160.995 179.379 161.005L179.31 161.008C171.761 160.498 164.267 159.304 156.931 157.453C156.84 157.43 156.769 157.372 156.731 157.293C156.696 157.219 156.695 157.139 156.713 157.071C156.732 157.004 156.774 156.936 156.84 156.891C156.91 156.843 156.999 156.826 157.089 156.849Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M96.8072 134.825C99.2507 134.248 101.779 134.187 104.257 134.517C109.532 135.218 114.363 137.577 118.9 140.187C120.073 140.861 121.235 141.556 122.396 142.248C123.558 142.94 124.718 143.63 125.891 144.299C128.296 145.67 130.748 146.959 133.24 148.167L133.298 148.203C133.352 148.245 133.388 148.302 133.402 148.368C133.419 148.45 133.399 148.529 133.364 148.589C133.304 148.692 133.169 148.781 133.015 148.743L132.948 148.719C130.187 147.379 127.474 145.942 124.819 144.403C122.227 142.902 119.703 141.327 117.089 139.883C112.44 137.314 107.409 135.153 102.058 134.952C97.622 134.786 93.5267 136.25 89.7613 138.537C86.3903 140.585 83.2826 143.201 79.985 145.551C76.6981 147.893 73.2422 149.953 69.2233 150.839C69.0242 150.882 68.8876 150.742 68.8512 150.609C68.8327 150.542 68.8333 150.461 68.8707 150.387C68.9106 150.307 68.9839 150.251 69.0758 150.231L69.4928 150.135C73.7826 149.086 77.5157 146.571 81.0582 143.958C84.3532 141.528 87.589 138.906 91.2477 136.973C93.0046 136.045 94.8634 135.284 96.8072 134.825Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M163.482 160.882L165.221 161.213C169.284 161.956 173.38 162.5 177.497 162.84C177.697 162.856 177.788 163.026 177.789 163.162C177.789 163.231 177.767 163.31 177.711 163.372C177.649 163.439 177.562 163.471 177.468 163.464C172.726 163.072 168.009 162.412 163.34 161.49V161.489C163.248 161.471 163.173 161.417 163.132 161.337C163.094 161.262 163.094 161.18 163.113 161.113C163.145 160.998 163.252 160.877 163.411 160.875L163.482 160.882Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M91.0405 139.674C94.768 137.669 98.8226 136.624 103.082 136.98C108.12 137.4 112.678 139.318 117.04 141.689C119.22 142.874 121.356 144.175 123.478 145.459C125.336 146.584 127.185 147.695 129.05 148.711L129.85 149.141L129.908 149.179C129.959 149.222 129.993 149.279 130.006 149.344C130.023 149.425 130.003 149.503 129.968 149.564C129.898 149.683 129.729 149.783 129.548 149.688C127.091 148.392 124.714 146.954 122.338 145.528C119.961 144.1 117.584 142.684 115.122 141.424C110.761 139.193 105.996 137.479 101.064 137.53C96.9466 137.572 93.1387 139.061 89.6479 141.198C86.4405 143.161 83.4835 145.615 80.3696 147.861C77.4585 149.96 74.4232 151.866 70.9682 152.962L70.271 153.171C70.1806 153.196 70.0909 153.181 70.019 153.133C69.9512 153.088 69.9096 153.019 69.8911 152.951C69.8726 152.884 69.8734 152.804 69.9077 152.731C69.9441 152.653 70.0127 152.594 70.103 152.569L70.4927 152.454C74.5016 151.24 78.0036 148.813 81.3647 146.322C84.4917 144.004 87.5788 141.536 91.0405 139.674Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M102.157 139.383C106.781 139.575 111.199 141.193 115.269 143.259C117.372 144.328 119.407 145.521 121.429 146.723C122.417 147.311 123.401 147.901 124.392 148.482C125.4 149.074 126.345 149.7 127.352 150.264V150.265C127.433 150.311 127.487 150.385 127.503 150.47C127.518 150.551 127.499 150.629 127.464 150.689C127.428 150.75 127.37 150.805 127.294 150.832C127.214 150.86 127.124 150.853 127.042 150.807C125.94 150.189 124.857 149.481 123.778 148.846C122.641 148.176 121.504 147.493 120.37 146.822C118.15 145.509 115.913 144.228 113.586 143.13C109.424 141.164 104.86 139.777 100.237 140.017C96.4229 140.215 92.881 141.678 89.6387 143.665C86.5918 145.532 83.7832 147.823 80.8536 149.963C77.9303 152.097 74.8964 154.072 71.4288 155.273V155.274C71.34 155.305 71.2484 155.295 71.1729 155.247C71.1023 155.202 71.0604 155.132 71.042 155.065C71.0062 154.933 71.051 154.744 71.2422 154.677L71.9668 154.412C75.5611 153.024 78.7399 150.743 81.834 148.436L84.0528 146.774C86.2791 145.113 88.5454 143.483 91 142.158C94.4488 140.296 98.202 139.219 102.157 139.383Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M101.563 141.958C105.972 142.02 110.036 143.355 113.905 145.196C117.77 147.034 121.456 149.387 125.092 151.485H125.093C125.174 151.532 125.225 151.607 125.241 151.692C125.256 151.772 125.236 151.849 125.201 151.909C125.166 151.97 125.108 152.025 125.031 152.052C124.95 152.08 124.859 152.073 124.778 152.026C120.607 149.619 116.588 146.984 112.177 145.085C108.268 143.402 103.998 142.309 99.7372 142.64C96.1858 142.916 92.8719 144.299 89.8329 146.136C86.9325 147.889 84.264 150.019 81.5135 152.054C78.7679 154.086 75.946 156.02 72.7596 157.358C72.6731 157.394 72.5779 157.391 72.4979 157.343C72.4243 157.298 72.3831 157.227 72.3651 157.16C72.3303 157.032 72.3697 156.846 72.5506 156.77C76.167 155.251 79.3481 152.887 82.4901 150.534C85.2756 148.447 88.0731 146.303 91.1727 144.655C94.3895 142.944 97.8866 141.907 101.563 141.958Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M100.727 144.555C108.92 144.337 116.168 148.773 122.892 152.776C122.973 152.824 123.024 152.899 123.039 152.983C123.053 153.063 123.034 153.14 122.999 153.201C122.963 153.262 122.905 153.317 122.828 153.344C122.746 153.372 122.655 153.362 122.574 153.314C118.686 150.999 114.81 148.622 110.581 147.03C106.949 145.663 103.036 144.864 99.1657 145.285C95.9171 145.638 92.8684 146.923 90.0652 148.592C87.3165 150.228 84.7865 152.201 82.2077 154.128C79.6326 156.052 77.0112 157.928 74.0954 159.35C73.9144 159.438 73.7485 159.338 73.6794 159.22C73.6443 159.161 73.6239 159.082 73.6413 159C73.6602 158.912 73.7189 158.84 73.8034 158.799L74.4138 158.491C77.4484 156.922 80.1929 154.851 82.9363 152.795C88.1941 148.853 93.8159 144.74 100.727 144.555Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M100.223 147.033C107.763 146.642 114.65 150.346 120.882 154.037C120.963 154.085 121.013 154.16 121.028 154.244C121.043 154.324 121.024 154.401 120.988 154.462C120.953 154.522 120.895 154.578 120.818 154.604C120.736 154.632 120.645 154.623 120.565 154.575C116.957 152.438 113.289 150.36 109.313 149.039C105.882 147.899 102.215 147.308 98.6271 147.81C95.664 148.225 92.874 149.403 90.2931 150.915C87.6989 152.434 85.3063 154.255 82.8918 156.067C80.6309 157.764 78.3518 159.452 75.8839 160.863L75.3878 161.141C75.3056 161.186 75.2151 161.192 75.1349 161.164C75.059 161.137 75.0013 161.082 74.966 161.021C74.9308 160.961 74.9113 160.883 74.9269 160.802C74.9437 160.717 74.9979 160.643 75.0802 160.597L75.6437 160.28C78.4444 158.67 81.0129 156.691 83.6105 154.749C88.5197 151.077 93.8239 147.365 100.223 147.033Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M99.798 149.651C106.624 149.15 113.05 152.122 118.782 155.404C118.864 155.451 118.917 155.525 118.933 155.61C118.948 155.691 118.928 155.769 118.893 155.829C118.857 155.89 118.799 155.944 118.723 155.971C118.642 155.999 118.551 155.992 118.47 155.945C115.165 154.053 111.76 152.313 108.104 151.257C104.927 150.339 101.564 149.931 98.2941 150.452C95.5515 150.89 92.9621 151.972 90.5529 153.356C88.1212 154.751 85.87 156.417 83.6203 158.105C81.3731 159.79 79.1266 161.498 76.715 162.966C76.6348 163.015 76.5438 163.025 76.4611 162.997C76.3831 162.971 76.3245 162.915 76.2892 162.855C76.254 162.794 76.2352 162.716 76.2492 162.637C76.264 162.553 76.3137 162.478 76.3937 162.429L76.9133 162.108C79.4958 160.486 81.9022 158.604 84.3625 156.778L85.2238 156.145C89.5555 152.987 94.2745 150.057 99.798 149.651Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M99.2913 152.286C105.392 151.676 111.309 153.949 116.558 156.757C116.641 156.802 116.696 156.876 116.713 156.962C116.729 157.043 116.709 157.122 116.674 157.182C116.604 157.301 116.434 157.401 116.253 157.304C113.245 155.694 110.118 154.299 106.802 153.503C103.913 152.811 100.891 152.576 97.9642 153.098C95.4398 153.549 93.0486 154.539 90.8119 155.797C88.5609 157.063 86.4657 158.567 84.39 160.115C82.3167 161.66 80.2597 163.252 78.0951 164.681C78.0165 164.733 77.9251 164.746 77.8402 164.719C77.7608 164.694 77.7024 164.638 77.6673 164.578C77.5983 164.459 77.594 164.264 77.764 164.152C80.2904 162.484 82.6613 160.609 85.1136 158.813C89.3233 155.731 93.9323 152.82 99.2913 152.286Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M98.6625 154.82C104.042 154.104 109.404 155.723 114.185 158.011C114.27 158.052 114.329 158.124 114.348 158.213C114.366 158.295 114.345 158.374 114.31 158.433C114.25 158.536 114.116 158.626 113.962 158.589L113.895 158.564C111.16 157.255 108.304 156.194 105.323 155.653C102.746 155.185 100.087 155.12 97.5219 155.642C95.2267 156.109 93.0456 157.017 90.9936 158.154C86.8807 160.432 83.3153 163.51 79.4487 166.219C79.3718 166.273 79.2805 166.29 79.1938 166.263C79.113 166.238 79.0539 166.182 79.019 166.122C78.9507 166.004 78.9452 165.811 79.1088 165.696V165.695C81.3594 164.118 83.4985 162.432 85.7368 160.818C89.5948 158.036 93.8221 155.464 98.6625 154.82Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M98.0012 157.511C102.663 156.723 107.409 157.754 111.712 159.497C111.895 159.571 111.937 159.758 111.902 159.888C111.883 159.954 111.842 160.026 111.769 160.07C111.69 160.118 111.595 160.122 111.508 160.087C109.068 159.098 106.524 158.346 103.913 158.028C101.632 157.75 99.3105 157.819 97.0725 158.333C95.0733 158.792 93.1626 159.58 91.3527 160.554C87.609 162.567 84.3138 165.283 80.8703 167.799C80.7947 167.854 80.7033 167.873 80.6154 167.847C80.5338 167.822 80.4743 167.765 80.4397 167.705C80.372 167.588 80.3647 167.396 80.5246 167.278H80.5256C82.5014 165.835 84.4495 164.316 86.4572 162.9L87.1115 162.444C90.4034 160.182 93.9927 158.188 98.0012 157.511Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M96.8939 160.322C100.838 159.411 104.922 159.873 108.743 161.03L108.806 161.057C108.865 161.089 108.909 161.137 108.936 161.195C108.969 161.268 108.971 161.347 108.952 161.415C108.934 161.482 108.892 161.551 108.824 161.597C108.75 161.645 108.66 161.658 108.57 161.631C106.372 160.965 104.107 160.524 101.814 160.464C98.0631 160.365 94.5039 161.427 91.2181 163.214C88.0637 164.93 85.219 167.147 82.3333 169.303C82.2581 169.359 82.1661 169.379 82.0775 169.353C81.9954 169.328 81.9363 169.27 81.9017 169.21C81.8345 169.094 81.8271 168.902 81.9857 168.784C83.691 167.51 85.3902 166.212 87.1517 165.003C90.1199 162.965 93.3341 161.145 96.8939 160.322Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M95.664 163.108C98.7891 162.172 102.05 162.106 105.244 162.647C105.337 162.663 105.415 162.715 105.457 162.797C105.496 162.872 105.496 162.954 105.477 163.021C105.441 163.151 105.308 163.291 105.112 163.257C103.258 162.943 101.373 162.826 99.5018 162.995C93.5041 163.535 88.4929 167.15 83.7724 170.683C83.6972 170.74 83.6053 170.76 83.5165 170.734C83.4343 170.71 83.3753 170.651 83.3407 170.591C83.2735 170.475 83.2663 170.284 83.4247 170.166L84.4725 169.384C85.524 168.606 86.5862 167.838 87.6747 167.107C90.1447 165.45 92.7855 163.969 95.664 163.108Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M100.237 164.909C100.331 164.904 100.417 164.937 100.477 165.004C100.532 165.066 100.554 165.144 100.554 165.214C100.553 165.351 100.46 165.521 100.257 165.533H100.256C95.9531 165.772 92.0721 167.572 88.4883 169.912C88.4098 169.963 88.3189 169.976 88.2344 169.949C88.155 169.923 88.0967 169.867 88.0616 169.806C87.9926 169.687 87.9883 169.492 88.1583 169.381L88.8477 168.94C92.3108 166.772 96.0764 165.14 100.237 164.909Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M131.878 150.999C134.781 150.234 137.788 150.193 140.718 150.828C146.82 152.151 152.029 155.899 157.039 159.342C162.154 162.857 167.687 166.424 173.978 166.947C174.178 166.964 174.27 167.134 174.27 167.27C174.271 167.339 174.249 167.418 174.192 167.481C174.131 167.547 174.044 167.579 173.95 167.571C173.227 167.511 172.507 167.416 171.793 167.28C165.66 166.116 160.372 162.406 155.379 158.953C150.335 155.466 144.887 151.848 138.704 151.121C135.68 150.766 132.676 151.24 129.83 152.314C126.929 153.408 124.25 155.02 121.625 156.686C118.941 158.389 116.269 160.113 113.489 161.674C110.628 163.281 107.698 164.764 104.722 166.142C98.8216 168.873 92.7384 171.187 86.5791 173.249C86.49 173.279 86.3982 173.268 86.3232 173.22C86.2531 173.175 86.2116 173.105 86.1933 173.038C86.1574 172.906 86.2025 172.716 86.3955 172.651L88.7275 171.858C94.9637 169.697 101.106 167.25 107.034 164.351C109.949 162.925 112.81 161.391 115.598 159.733C116.951 158.928 118.277 158.079 119.605 157.229C120.933 156.378 122.265 155.526 123.623 154.717C126.208 153.176 128.937 151.774 131.878 150.999Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M131.722 154.371C134.437 153.634 137.254 153.517 140.021 154.021C145.717 155.059 150.732 158.328 155.382 161.532C160.152 164.819 165.019 168.277 170.661 169.721L171.21 169.853L171.274 169.876C171.335 169.904 171.383 169.951 171.413 170.011C171.449 170.085 171.45 170.165 171.431 170.232C171.399 170.349 171.289 170.471 171.129 170.468L171.057 170.459C170.343 170.294 169.637 170.099 168.94 169.871C163.281 168.018 158.494 164.366 153.651 161.106C148.914 157.918 143.708 154.813 137.931 154.377C132.092 153.936 126.864 156.712 122.027 159.76C119.536 161.329 117.069 162.951 114.507 164.424C111.852 165.951 109.135 167.369 106.374 168.692C101.591 170.985 96.6766 172.989 91.694 174.797L89.5544 175.56C89.4657 175.592 89.3735 175.582 89.2975 175.534C89.2267 175.489 89.185 175.42 89.1667 175.352C89.1307 175.221 89.1754 175.032 89.3659 174.965C90.0892 174.711 90.8108 174.455 91.5309 174.193C97.3497 172.083 103.073 169.698 108.595 166.903C111.321 165.524 113.998 164.045 116.606 162.455C119.084 160.945 121.517 159.277 124.034 157.803C126.449 156.387 128.995 155.112 131.722 154.371Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M131.719 157.705C134.2 157.031 136.773 156.867 139.319 157.239C144.614 158.014 149.369 160.825 153.712 163.732C158.418 166.881 162.942 170.343 168.264 172.311C168.451 172.38 168.495 172.568 168.459 172.699C168.441 172.766 168.399 172.837 168.327 172.882C168.25 172.93 168.157 172.937 168.069 172.904C162.156 170.718 157.226 166.725 152.026 163.371C147.603 160.518 142.71 157.919 137.386 157.678C132.028 157.435 127.176 159.894 122.702 162.669C120.381 164.109 118.094 165.641 115.734 167.039C113.326 168.466 110.864 169.799 108.361 171.051C103.29 173.588 98.0552 175.784 92.7403 177.748C92.6522 177.781 92.5595 177.774 92.4825 177.726C92.4107 177.681 92.3688 177.61 92.3506 177.543C92.3152 177.412 92.3582 177.224 92.546 177.154C93.2155 176.907 93.8841 176.657 94.5508 176.402C99.9161 174.352 105.185 172.043 110.27 169.372C112.792 168.047 115.268 166.633 117.683 165.121C119.95 163.702 122.183 162.191 124.516 160.855C126.784 159.554 129.174 158.397 131.719 157.705Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M132.029 160.957C134.353 160.364 136.755 160.221 139.133 160.546C143.957 161.205 148.352 163.587 152.361 166.173C156.787 169.027 160.908 172.263 165.645 174.534C165.73 174.575 165.789 174.646 165.809 174.734C165.826 174.817 165.806 174.896 165.77 174.956C165.701 175.074 165.535 175.173 165.354 175.086C162.722 173.824 160.277 172.266 157.878 170.647C155.475 169.027 153.124 167.35 150.662 165.84C146.535 163.308 141.971 161.135 137.082 160.999C132.175 160.863 127.671 162.993 123.528 165.499C122.464 166.143 121.415 166.814 120.365 167.487C119.316 168.161 118.267 168.837 117.202 169.49C114.999 170.842 112.746 172.114 110.457 173.313C106.397 175.441 102.219 177.337 97.9736 179.059L96.1503 179.786C96.0629 179.821 95.969 179.816 95.8906 179.768C95.818 179.723 95.7768 179.652 95.7587 179.585C95.7236 179.455 95.7654 179.268 95.9501 179.195L98.04 178.359C102.904 176.376 107.672 174.16 112.274 171.63C114.565 170.37 116.807 169.027 119.009 167.616C121.072 166.294 123.133 164.943 125.29 163.756C127.417 162.585 129.659 161.563 132.029 160.957Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M125.801 166.801C129.775 164.716 133.991 163.293 138.665 163.821C143.375 164.353 147.508 166.409 151.433 168.882C153.395 170.118 155.31 171.462 157.218 172.772C159.129 174.083 161.034 175.36 162.984 176.473C163.065 176.52 163.118 176.594 163.134 176.679C163.149 176.76 163.13 176.837 163.094 176.898C163.059 176.958 163.001 177.013 162.924 177.039C162.843 177.068 162.753 177.061 162.671 177.014C160.362 175.696 158.171 174.198 155.989 172.702C153.804 171.204 151.628 169.707 149.34 168.386C145.514 166.177 141.3 164.394 136.848 164.328C132.357 164.263 128.186 166.095 124.35 168.342C122.467 169.445 120.646 170.647 118.799 171.824C116.761 173.123 114.68 174.354 112.562 175.518C108.412 177.799 104.127 179.822 99.7642 181.655C99.6777 181.691 99.5834 181.688 99.5034 181.64C99.4297 181.595 99.3885 181.524 99.3706 181.457C99.3359 181.329 99.3756 181.143 99.5562 181.067C104.629 178.936 109.592 176.541 114.356 173.79C116.223 172.713 118.088 171.488 119.988 170.27C121.885 169.053 123.815 167.844 125.801 166.801Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M126.693 169.704C130.272 167.916 134.071 166.725 138.27 167.113C142.438 167.499 146.186 169.129 149.721 171.199C153.248 173.264 156.598 175.788 159.926 177.943C160.097 178.054 160.092 178.249 160.023 178.368C159.988 178.429 159.93 178.485 159.851 178.511C159.767 178.538 159.677 178.526 159.597 178.475H159.596C155.688 175.944 152.023 173.12 147.929 170.938C144.452 169.084 140.65 167.683 136.69 167.66C132.721 167.638 128.977 169.098 125.524 171.005C123.74 171.991 122.016 173.079 120.294 174.179C118.573 175.278 116.853 176.391 115.084 177.423C111.368 179.589 107.53 181.537 103.615 183.311C103.434 183.393 103.27 183.291 103.202 183.175C103.167 183.115 103.146 183.036 103.165 182.953C103.185 182.864 103.246 182.793 103.332 182.754C107.819 180.721 112.203 178.459 116.414 175.904C119.742 173.885 123.122 171.489 126.693 169.704Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M118.869 177.701C124.665 173.991 130.886 169.794 138.19 170.438C145.466 171.08 151.616 175.631 157.387 179.583C157.553 179.697 157.548 179.891 157.479 180.009C157.444 180.069 157.386 180.125 157.306 180.15C157.22 180.177 157.128 180.162 157.051 180.109C153.73 177.835 150.458 175.502 146.859 173.72C143.706 172.158 140.293 171.03 136.766 170.993C133.259 170.957 129.896 172.082 126.782 173.668C123.649 175.264 120.765 177.272 117.767 179.138C114.892 180.926 111.934 182.575 108.916 184.104L107.619 184.753C107.438 184.842 107.271 184.742 107.202 184.624C107.167 184.564 107.147 184.485 107.164 184.402C107.183 184.315 107.241 184.243 107.325 184.201C111.291 182.246 115.145 180.084 118.869 177.701Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M121.086 179.612C126.173 176.375 131.754 173.248 138.062 173.762C144.31 174.271 149.846 177.75 154.85 181.169H154.849C155.016 181.283 155.011 181.477 154.942 181.595C154.907 181.655 154.848 181.711 154.768 181.736C154.682 181.763 154.591 181.748 154.514 181.695C151.641 179.732 148.702 177.839 145.528 176.428C142.742 175.189 139.753 174.346 136.702 174.326C133.673 174.306 130.729 175.148 127.983 176.417C125.262 177.674 122.73 179.296 120.201 180.921C117.787 182.471 115.344 183.966 112.836 185.36L111.757 185.952C111.576 186.05 111.406 185.951 111.336 185.831C111.301 185.771 111.281 185.692 111.297 185.611C111.314 185.525 111.369 185.452 111.452 185.407L112.684 184.729C115.545 183.124 118.312 181.378 121.086 179.612Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M137.291 177.046C142.497 177.248 147.291 179.652 151.59 182.346H151.589C151.763 182.455 151.76 182.652 151.69 182.772C151.655 182.832 151.597 182.888 151.519 182.914C151.456 182.934 151.389 182.933 151.326 182.91L151.265 182.879C148.823 181.349 146.288 179.949 143.582 178.972C141.251 178.131 138.798 177.621 136.322 177.659C133.888 177.697 131.505 178.288 129.246 179.202C126.892 180.153 124.687 181.41 122.523 182.757C120.366 184.101 118.233 185.545 116.055 186.847C115.974 186.895 115.883 186.905 115.801 186.877C115.723 186.85 115.665 186.795 115.63 186.735C115.595 186.674 115.576 186.597 115.59 186.517C115.605 186.433 115.656 186.357 115.736 186.309C117.018 185.543 118.277 184.739 119.537 183.932C120.797 183.126 122.058 182.317 123.343 181.544C127.56 179.005 132.214 176.848 137.291 177.046Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M137.004 180.37C141.054 180.463 144.898 181.931 148.407 183.813C148.49 183.858 148.545 183.931 148.562 184.017C148.578 184.098 148.558 184.177 148.523 184.237C148.453 184.356 148.284 184.455 148.103 184.359H148.102C146.119 183.296 144.059 182.374 141.898 181.762C138.121 180.693 134.251 180.783 130.537 182.053C126.904 183.296 123.63 185.388 120.395 187.465H120.394C120.315 187.515 120.224 187.528 120.14 187.501C120.061 187.475 120.003 187.418 119.968 187.358C119.899 187.239 119.895 187.043 120.067 186.932L121.513 186.009C122.965 185.091 124.436 184.196 125.958 183.391C129.363 181.593 133.084 180.279 137.004 180.37Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M134.051 183.883C137.426 183.398 140.797 183.956 143.965 185.114C144.153 185.183 144.197 185.371 144.162 185.503C144.144 185.57 144.101 185.64 144.03 185.685C143.973 185.721 143.906 185.734 143.84 185.725L143.772 185.709C140.642 184.564 137.318 184.014 134.013 184.518C130.855 185 127.907 186.327 125.124 187.892C125.042 187.938 124.951 187.945 124.871 187.917C124.794 187.89 124.736 187.835 124.701 187.774C124.666 187.714 124.647 187.636 124.662 187.555C124.678 187.47 124.731 187.396 124.813 187.35L125.352 187.051C128.063 185.569 130.956 184.327 134.051 183.883Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M151.539 122.305C155.761 119.891 160.479 118.16 165.427 119.266C167.933 119.827 170.34 120.866 172.646 121.932H172.647C174.854 122.952 177 124.104 179.07 125.378C183.268 127.962 187.148 131.05 190.614 134.554C190.68 134.621 190.71 134.707 190.704 134.792C190.697 134.873 190.659 134.943 190.61 134.993C190.56 135.042 190.49 135.081 190.409 135.087C190.322 135.094 190.238 135.061 190.172 134.995C186.334 131.114 181.985 127.754 177.258 125.026C175.165 123.817 172.999 122.732 170.776 121.782C168.33 120.735 165.791 119.74 163.144 119.552C158.244 119.205 153.708 121.626 149.637 124.18C147.516 125.511 145.424 126.929 143.268 128.224C141.044 129.56 138.769 130.815 136.443 131.967C131.856 134.239 127.056 136.119 122.075 137.342H122.074C121.983 137.364 121.895 137.347 121.825 137.298C121.759 137.253 121.718 137.185 121.699 137.118C121.68 137.05 121.681 136.969 121.717 136.895C121.756 136.816 121.827 136.759 121.919 136.737L122.994 136.462C128.357 135.037 133.504 132.862 138.406 130.259C140.681 129.051 142.906 127.748 145.083 126.371C147.222 125.017 149.328 123.57 151.539 122.305Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M159.208 121.85C161.511 121.394 163.831 121.491 166.097 122.086C168.412 122.695 170.646 123.658 172.799 124.661C174.917 125.647 176.979 126.756 178.971 127.977C182.967 130.426 186.676 133.333 190.017 136.619C190.084 136.685 190.117 136.77 190.111 136.857C190.105 136.938 190.065 137.009 190.016 137.058C189.966 137.108 189.896 137.146 189.816 137.152C189.73 137.159 189.645 137.129 189.578 137.063C185.858 133.403 181.677 130.223 177.152 127.623C175.183 126.491 173.151 125.469 171.069 124.565C168.825 123.591 166.512 122.66 164.102 122.311C159.375 121.627 154.873 123.671 150.87 126.073C148.845 127.288 146.913 128.616 144.9 129.876C142.816 131.178 140.688 132.411 138.515 133.559C134.202 135.837 129.699 137.781 125.022 139.192C124.932 139.219 124.841 139.207 124.768 139.159C124.699 139.113 124.657 139.045 124.639 138.977C124.62 138.91 124.622 138.831 124.655 138.757C124.691 138.68 124.757 138.62 124.847 138.593L125.855 138.28C130.878 136.673 135.696 134.458 140.298 131.878C142.466 130.663 144.583 129.36 146.666 128.001C148.669 126.696 150.682 125.379 152.809 124.258L153.57 123.869C155.357 122.979 157.237 122.24 159.208 121.85Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M160.174 124.343C162.41 124.038 164.621 124.288 166.769 124.928C168.949 125.577 171.061 126.49 173.105 127.456C175.131 128.413 177.104 129.481 179.013 130.653C182.796 132.977 186.322 135.71 189.522 138.786C189.59 138.851 189.624 138.936 189.618 139.023C189.613 139.106 189.573 139.176 189.523 139.226C189.474 139.275 189.405 139.314 189.324 139.32C189.239 139.327 189.153 139.298 189.086 139.233C185.518 135.803 181.543 132.808 177.256 130.334C175.373 129.247 173.432 128.26 171.443 127.381C169.315 126.44 167.13 125.542 164.858 125.094C160.4 124.215 156.087 125.777 152.198 127.957C150.28 129.032 148.445 130.245 146.596 131.448C144.631 132.726 142.627 133.945 140.581 135.091C136.544 137.352 132.34 139.33 127.975 140.881H127.974C127.885 140.912 127.793 140.905 127.717 140.856C127.646 140.811 127.604 140.741 127.586 140.674C127.55 140.542 127.594 140.354 127.784 140.286C132.853 138.485 137.701 136.1 142.332 133.361C144.296 132.2 146.195 130.92 148.13 129.681C150.062 128.443 152.026 127.248 154.107 126.262C156.024 125.354 158.054 124.632 160.174 124.343Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M161.106 126.896C163.271 126.733 165.39 127.118 167.43 127.788C171.454 129.109 175.328 131.053 178.927 133.256C182.508 135.448 185.86 138.005 188.925 140.873C188.994 140.937 189.028 141.022 189.023 141.11C189.018 141.193 188.979 141.263 188.929 141.312C188.844 141.398 188.688 141.45 188.549 141.367L188.492 141.323C185.07 138.122 181.289 135.314 177.23 132.974C175.451 131.948 173.62 131.011 171.746 130.17C169.754 129.276 167.716 128.428 165.605 127.912C163.492 127.396 161.33 127.353 159.201 127.795C157.226 128.205 155.338 128.97 153.538 129.89C151.653 130.852 149.854 131.97 148.068 133.12C146.284 134.269 144.508 135.451 142.678 136.534C138.875 138.785 134.925 140.793 130.826 142.453C130.739 142.488 130.645 142.484 130.566 142.436C130.493 142.391 130.451 142.319 130.433 142.253C130.398 142.124 130.439 141.937 130.622 141.863C135.392 139.932 139.96 137.518 144.332 134.8C147.9 132.581 151.359 130.034 155.29 128.386C157.137 127.611 159.092 127.046 161.106 126.896Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M162.442 129.515C164.386 129.555 166.277 130.028 168.091 130.671C175.52 133.306 182.415 137.611 188.238 142.894C188.308 142.957 188.343 143.042 188.338 143.131C188.334 143.214 188.295 143.285 188.246 143.334C188.148 143.431 187.959 143.485 187.808 143.348C184.571 140.411 181.028 137.821 177.242 135.637C173.843 133.677 170.198 131.88 166.432 130.79C164.501 130.231 162.519 130.001 160.525 130.236C158.605 130.463 156.74 131.069 154.971 131.86C151.408 133.453 148.242 135.749 144.919 137.828C141.354 140.059 137.657 142.085 133.826 143.826C133.645 143.908 133.481 143.808 133.413 143.691C133.378 143.632 133.357 143.552 133.376 143.469C133.396 143.379 133.457 143.309 133.543 143.271L134.364 142.892C138.46 140.978 142.4 138.738 146.193 136.275C149.461 134.153 152.767 131.907 156.492 130.575C158.272 129.939 160.148 129.531 162.052 129.513L162.442 129.515Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M168.662 133.532C175.62 136.134 182.075 140.094 187.635 145.005L187.682 145.056C187.722 145.11 187.742 145.175 187.739 145.242C187.735 145.325 187.694 145.397 187.645 145.446C187.548 145.542 187.361 145.596 187.21 145.462C184.111 142.726 180.743 140.302 177.159 138.242C173.971 136.409 170.588 134.766 167.088 133.643C165.251 133.054 163.363 132.708 161.441 132.81C159.713 132.902 158.015 133.323 156.39 133.93C152.983 135.203 149.92 137.244 146.867 139.235C143.574 141.382 140.179 143.371 136.66 145.13H136.659C136.478 145.22 136.311 145.121 136.242 145.002C136.207 144.942 136.186 144.864 136.204 144.782C136.222 144.694 136.28 144.622 136.364 144.581L137.114 144.2C140.852 142.279 144.41 140.073 147.946 137.788C150.984 135.824 154.16 133.864 157.69 132.839C159.409 132.341 161.209 132.084 163.006 132.189C164.962 132.304 166.852 132.855 168.662 133.532Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M158.826 135.2C160.439 134.842 162.108 134.719 163.757 134.908C165.564 135.116 167.304 135.664 168.983 136.308C175.427 138.779 181.425 142.38 186.686 146.838C186.758 146.899 186.797 146.983 186.794 147.073C186.79 147.158 186.749 147.23 186.7 147.279C186.603 147.374 186.418 147.429 186.265 147.3V147.299C183.327 144.809 180.161 142.595 176.81 140.697C173.843 139.017 170.72 137.525 167.492 136.431C165.802 135.859 164.062 135.457 162.284 135.444C160.684 135.432 159.094 135.71 157.561 136.182C154.378 137.162 151.483 138.908 148.677 140.71C145.645 142.657 142.621 144.605 139.445 146.331C139.363 146.376 139.273 146.381 139.193 146.353C139.117 146.326 139.06 146.271 139.024 146.21C138.989 146.15 138.969 146.071 138.985 145.99C139.002 145.904 139.057 145.831 139.139 145.786C142.78 143.807 146.188 141.501 149.719 139.313C152.535 137.568 155.54 135.93 158.826 135.2Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M160.153 137.615C163.438 137.162 166.541 137.988 169.548 139.182C175.471 141.534 181.001 144.818 185.927 148.857C186 148.916 186.041 149.001 186.037 149.092C186.034 149.177 185.994 149.249 185.945 149.297C185.861 149.381 185.709 149.434 185.57 149.361L185.511 149.322C182.787 147.087 179.875 145.086 176.809 143.35C174.061 141.794 171.187 140.428 168.222 139.342C166.652 138.767 165.036 138.296 163.379 138.149C161.881 138.015 160.37 138.145 158.901 138.477C155.935 139.148 153.195 140.584 150.594 142.163C149.211 143.002 147.854 143.883 146.492 144.761C145.129 145.639 143.762 146.514 142.361 147.338C142.28 147.386 142.189 147.395 142.107 147.367C142.03 147.34 141.972 147.285 141.937 147.224C141.902 147.164 141.882 147.086 141.897 147.006C141.912 146.922 141.963 146.847 142.044 146.799C143.658 145.849 145.229 144.827 146.807 143.808C148.384 142.79 149.967 141.775 151.603 140.843C154.242 139.34 157.095 138.036 160.153 137.615Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M161.026 140.173C164.091 139.926 166.996 140.81 169.778 141.929C175.268 144.136 180.414 147.142 185.055 150.809C185.129 150.867 185.172 150.951 185.17 151.043C185.167 151.129 185.126 151.201 185.077 151.249C184.981 151.344 184.799 151.4 184.644 151.278C182.387 149.495 180.009 147.869 177.526 146.418L176.456 145.807C173.913 144.39 171.267 143.142 168.544 142.116C165.73 141.057 162.854 140.454 159.876 140.941C157.087 141.396 154.525 142.533 152.066 143.928C149.909 145.152 147.85 146.564 145.782 147.892L144.895 148.454C144.816 148.504 144.725 148.516 144.641 148.489C144.563 148.463 144.505 148.407 144.469 148.347C144.4 148.228 144.396 148.031 144.57 147.921L145.622 147.253C148.07 145.681 150.494 144.029 153.094 142.686C155.559 141.413 158.221 140.399 161.026 140.173Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M162.353 142.783C165.147 142.816 167.8 143.762 170.323 144.805C175.27 146.85 179.934 149.544 184.19 152.788H184.191C184.347 152.908 184.338 153.098 184.271 153.214C184.236 153.274 184.178 153.333 184.095 153.357C184.006 153.383 183.914 153.361 183.839 153.304C181.509 151.527 179.057 149.913 176.498 148.485C174.192 147.197 171.803 146.057 169.348 145.082C166.841 144.087 164.262 143.3 161.566 143.42C157.073 143.621 153.02 145.9 149.243 148.311L147.64 149.346C147.561 149.397 147.47 149.411 147.385 149.383C147.306 149.357 147.248 149.301 147.213 149.241C147.144 149.122 147.14 148.927 147.311 148.816L148.254 148.204C150.304 146.884 152.399 145.602 154.634 144.597L155.153 144.369C157.421 143.404 159.858 142.753 162.353 142.783Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M157.099 146.289C159.263 145.588 161.563 145.259 163.842 145.535C166.267 145.828 168.571 146.718 170.784 147.653C174.89 149.388 178.796 151.575 182.432 154.15L183.155 154.67L183.208 154.717C183.313 154.834 183.299 154.994 183.24 155.096C183.206 155.156 183.147 155.214 183.065 155.239C182.978 155.265 182.885 155.246 182.81 155.191C180.704 153.655 178.507 152.247 176.225 150.988C174.196 149.869 172.104 148.864 169.961 147.985C167.712 147.063 165.392 146.223 162.974 146.083C158.402 145.819 154.153 147.947 150.315 150.295C150.235 150.344 150.144 150.354 150.062 150.327C149.984 150.3 149.926 150.245 149.891 150.185C149.855 150.124 149.836 150.048 149.85 149.968C149.864 149.884 149.914 149.808 149.994 149.759H149.995C152.236 148.388 154.576 147.107 157.099 146.289Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M158.826 148.472C160.597 148.079 162.426 147.977 164.228 148.241C166.185 148.528 168.057 149.196 169.867 149.928C173.476 151.384 176.936 153.188 180.207 155.295H180.208C180.38 155.406 180.375 155.601 180.306 155.72C180.271 155.781 180.213 155.837 180.134 155.863C180.05 155.891 179.958 155.878 179.879 155.827C178.115 154.69 176.297 153.64 174.427 152.688C172.719 151.819 170.97 151.029 169.188 150.327C167.416 149.63 165.6 149.018 163.719 148.802C160.02 148.376 156.43 149.611 153.163 151.318C152.982 151.413 152.814 151.314 152.744 151.194C152.709 151.134 152.689 151.055 152.706 150.973C152.724 150.887 152.781 150.814 152.864 150.771H152.863C154.748 149.786 156.731 148.938 158.826 148.472Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M157.453 151.492C160.26 150.657 163.141 150.519 166.013 151.261C169.073 152.051 172.026 153.42 174.818 154.842H174.819C174.902 154.885 174.959 154.958 174.977 155.045C174.994 155.126 174.973 155.204 174.938 155.265C174.868 155.383 174.701 155.483 174.52 155.391C171.8 154.005 168.959 152.695 166.018 151.908C163.205 151.156 160.393 151.271 157.627 152.092L157.075 152.266C156.985 152.295 156.894 152.284 156.819 152.236C156.749 152.191 156.707 152.122 156.689 152.055C156.653 151.922 156.699 151.732 156.892 151.668L157.453 151.492Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M116.9 98.7478C124.129 95.92 132.099 98.4854 138.312 102.402C145.027 106.636 150.726 112.234 156.635 117.428C156.706 117.49 156.744 117.575 156.739 117.665C156.735 117.748 156.695 117.819 156.646 117.868C156.549 117.965 156.362 118.019 156.21 117.885C155.489 117.252 154.775 116.609 154.066 115.964C151.165 113.322 148.248 110.726 145.206 108.254C142.319 105.906 139.353 103.634 136.112 101.819L135.46 101.463C128.925 97.9718 120.566 96.5797 114.122 100.914C112.691 101.876 111.388 103.028 110.011 104.119C108.64 105.204 107.206 106.22 105.543 106.879C103.644 107.63 101.661 107.794 99.6749 107.757C98.6818 107.738 97.6846 107.668 96.6964 107.594C95.7069 107.521 94.7268 107.445 93.7599 107.41C91.9426 107.345 90.1336 107.439 88.3849 107.911C87.0247 108.278 85.7243 108.838 84.4923 109.528C81.9876 110.93 79.7909 112.816 77.6398 114.73C73.4765 118.435 69.1318 122.669 63.3449 123.683C63.1484 123.718 63.0158 123.578 62.9796 123.447C62.9612 123.38 62.9611 123.299 63.0001 123.223C63.0421 123.143 63.1186 123.089 63.2111 123.073C63.9053 122.951 64.5903 122.786 65.2609 122.571C68.0562 121.676 70.4708 120.066 72.7443 118.212C73.881 117.285 74.9807 116.298 76.0744 115.31C77.1669 114.324 78.2545 113.336 79.3634 112.41C81.5037 110.623 83.8087 108.955 86.4318 107.906C87.8438 107.341 89.3276 106.981 90.8429 106.842C92.8874 106.656 94.9401 106.84 96.9581 106.985C98.9966 107.132 101.048 107.26 103.049 106.92C104.906 106.604 106.58 105.82 108.129 104.746C109.586 103.735 110.911 102.581 112.32 101.496C113.722 100.416 115.191 99.4161 116.9 98.7478Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M115.539 102.121C122.212 98.7567 130.151 100.551 136.338 103.997C139.733 105.888 142.818 108.271 145.813 110.711C148.81 113.153 151.681 115.715 154.535 118.315C154.605 118.378 154.641 118.463 154.636 118.551C154.631 118.634 154.592 118.705 154.542 118.754C154.445 118.851 154.257 118.905 154.106 118.768C153.447 118.168 152.788 117.563 152.131 116.963C149.277 114.359 146.324 111.864 143.282 109.482C140.229 107.091 137.024 104.86 133.495 103.258C130.135 101.732 126.455 100.844 122.761 100.975C120.976 101.038 119.199 101.348 117.519 101.949C115.828 102.555 114.308 103.485 112.878 104.579C112.2 105.098 111.535 105.645 110.862 106.191C110.189 106.735 109.509 107.278 108.806 107.782C107.399 108.792 105.893 109.655 104.15 110.093C102.222 110.578 100.266 110.584 98.3196 110.475C96.3641 110.366 94.4359 110.142 92.5169 110.156C89.4443 110.179 86.5975 111.028 83.9631 112.601C81.5343 114.051 79.3936 115.93 77.2766 117.822C73.2327 121.435 68.9633 125.464 63.345 126.448L63.346 126.449C63.1492 126.484 63.016 126.345 62.9797 126.214C62.9612 126.147 62.9615 126.066 63.0003 125.99C63.0421 125.91 63.1185 125.856 63.2112 125.84L63.7141 125.743C64.2147 125.639 64.7097 125.512 65.1975 125.358H65.1985C67.919 124.5 70.2822 122.957 72.5032 121.17C73.6135 120.277 74.6864 119.324 75.7503 118.365C76.8131 117.408 77.8681 116.444 78.9378 115.534C81.0471 113.741 83.303 112.034 85.8713 110.907C87.1276 110.356 88.4488 109.949 89.8088 109.737C91.7681 109.43 93.7533 109.524 95.7024 109.66C97.6673 109.798 99.6187 109.987 101.568 109.857C103.492 109.728 105.314 109.22 106.978 108.249C108.484 107.371 109.82 106.249 111.187 105.136C112.549 104.027 113.941 102.928 115.539 102.121Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M114.16 105.672C120.288 101.762 128.142 102.761 134.374 105.743C137.779 107.372 140.873 109.56 143.822 111.876C146.836 114.245 149.764 116.72 152.596 119.304C152.665 119.368 152.702 119.452 152.697 119.541C152.692 119.624 152.652 119.694 152.603 119.744C152.505 119.841 152.317 119.894 152.166 119.756C151.515 119.162 150.861 118.571 150.202 117.987C147.319 115.431 144.325 112.957 141.22 110.678C138.189 108.454 134.95 106.445 131.413 105.17C128.054 103.96 124.393 103.409 120.85 103.901C119.135 104.139 117.45 104.628 115.903 105.4C114.357 106.17 112.987 107.23 111.637 108.328C110.362 109.364 109.086 110.446 107.67 111.325C106.168 112.257 104.532 112.875 102.775 113.122C100.886 113.386 98.9982 113.291 97.1253 113.157C95.2472 113.022 93.3876 112.849 91.5286 112.949C85.6919 113.263 81.2199 117.054 77.0061 120.826C73.0483 124.368 68.8376 128.25 63.345 129.212C63.1485 129.247 63.0159 129.108 62.9797 128.977C62.9613 128.91 62.961 128.828 63.0003 128.752C63.0424 128.672 63.1196 128.619 63.2122 128.603L63.7004 128.509C64.1873 128.409 64.6689 128.285 65.1438 128.137C67.8093 127.307 70.1344 125.816 72.3167 124.08C74.5058 122.338 76.5261 120.372 78.6184 118.572C80.6627 116.814 82.8303 115.121 85.2893 113.939C86.5004 113.356 87.7763 112.902 89.0969 112.629C90.8911 112.258 92.7233 112.252 94.5286 112.349C96.4518 112.452 98.3957 112.672 100.325 112.659C102.248 112.645 104.131 112.398 105.902 111.577C107.435 110.865 108.776 109.854 110.098 108.785C111.415 107.719 112.719 106.592 114.16 105.672Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M122.6 105.885C126.077 105.737 129.571 106.469 132.77 107.791C136.131 109.181 139.204 111.153 142.099 113.317C145.073 115.541 147.954 117.92 150.731 120.383C150.801 120.445 150.839 120.53 150.834 120.619C150.83 120.703 150.791 120.774 150.742 120.824C150.644 120.92 150.457 120.974 150.305 120.839C149.626 120.237 148.942 119.642 148.249 119.057C142.723 114.386 136.852 109.443 129.817 107.418C126.491 106.461 122.918 106.163 119.53 106.904C117.886 107.263 116.302 107.873 114.863 108.741C113.431 109.603 112.148 110.683 110.84 111.747C109.605 112.753 108.335 113.762 106.914 114.534C105.438 115.335 103.835 115.793 102.171 115.961C100.299 116.15 98.4312 116.02 96.5794 115.882C94.7227 115.744 92.8821 115.598 91.0423 115.747C85.4464 116.201 81.0994 119.933 77.0403 123.566C73.1781 127.022 69.0933 130.806 63.7825 131.898C63.5843 131.939 63.449 131.799 63.4124 131.667C63.3938 131.6 63.3943 131.518 63.4319 131.444C63.4723 131.364 63.5469 131.309 63.639 131.29L64.1263 131.182C64.6117 131.067 65.0916 130.93 65.5638 130.768C68.1415 129.886 70.3959 128.397 72.5188 126.688C74.6496 124.972 76.6207 123.057 78.6712 121.296C80.6615 119.587 82.7684 117.943 85.1497 116.774C86.3292 116.194 87.572 115.735 88.8587 115.447C90.5615 115.065 92.305 115.017 94.0286 115.09C94.9528 115.129 95.8889 115.205 96.8235 115.275C97.7596 115.345 98.696 115.41 99.6272 115.43C101.49 115.469 103.324 115.327 105.077 114.686C106.649 114.112 108.021 113.186 109.345 112.156C110.66 111.132 111.948 109.987 113.307 109.019C116.05 107.064 119.243 106.028 122.6 105.885Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M121.224 108.758C124.547 108.376 127.949 108.868 131.098 109.929C137.852 112.204 143.521 116.996 148.844 121.507C148.915 121.568 148.954 121.652 148.95 121.742C148.946 121.827 148.906 121.898 148.857 121.947C148.761 122.043 148.576 122.098 148.423 121.969C147.74 121.39 147.048 120.82 146.355 120.25C140.931 115.79 135.149 111.277 128.181 109.773C124.908 109.066 121.44 109.027 118.231 110.009C116.676 110.485 115.224 111.211 113.887 112.139C112.547 113.068 111.325 114.139 110.034 115.161C108.824 116.119 107.551 117.042 106.125 117.694C104.589 118.394 102.935 118.712 101.266 118.793C99.4507 118.882 97.6365 118.718 95.8406 118.587C94.04 118.456 92.2545 118.357 90.4724 118.57C85.1335 119.205 80.9566 122.827 77.0525 126.32C73.2808 129.695 69.3099 133.383 64.1697 134.58V134.579C63.9698 134.626 63.8325 134.487 63.7957 134.354C63.7771 134.286 63.7777 134.205 63.8142 134.131C63.8434 134.072 63.8912 134.025 63.9519 133.996L64.0174 133.974C64.6275 133.832 65.2288 133.657 65.8172 133.445C68.3196 132.541 70.5152 131.073 72.5867 129.398C74.6655 127.717 76.5929 125.85 78.5965 124.125C80.5469 122.446 82.6067 120.828 84.9265 119.652C87.5814 118.304 90.3955 117.765 93.3572 117.833C94.2674 117.854 95.1841 117.919 96.0994 117.988C97.016 118.058 97.9307 118.132 98.8435 118.173C100.669 118.255 102.473 118.203 104.229 117.721C105.809 117.287 107.195 116.482 108.511 115.532C109.834 114.577 111.058 113.498 112.356 112.493C113.632 111.507 114.98 110.618 116.48 109.983C117.994 109.342 119.596 108.945 121.224 108.758Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M119.907 111.721C123.052 111.132 126.316 111.379 129.391 112.183C136.039 113.921 141.58 118.28 146.757 122.535C146.829 122.595 146.871 122.679 146.867 122.771C146.864 122.855 146.823 122.927 146.774 122.976C146.678 123.071 146.494 123.127 146.34 123C145.685 122.462 145.028 121.925 144.367 121.396C139.102 117.175 133.311 113.206 126.525 112.239C123.345 111.786 120.031 112.005 117.039 113.192C115.581 113.771 114.29 114.607 113.05 115.538C112.429 116.004 111.822 116.493 111.213 116.984C110.605 117.476 109.993 117.97 109.367 118.445C108.173 119.351 106.898 120.181 105.481 120.731C103.898 121.345 102.201 121.559 100.526 121.581C98.7639 121.605 96.9945 121.426 95.2471 121.309C93.4941 121.191 91.7547 121.134 90.0254 121.394C84.9343 122.158 80.9166 125.631 77.1631 128.988C73.4822 132.28 69.6431 135.881 64.6895 137.217C64.5988 137.241 64.5089 137.225 64.4375 137.177C64.3702 137.131 64.329 137.063 64.3106 136.995C64.2922 136.928 64.293 136.848 64.3281 136.774C64.3655 136.697 64.435 136.638 64.5254 136.613C65.134 136.449 65.7332 136.253 66.3184 136.02C68.7211 135.063 70.8372 133.602 72.8418 131.958C74.8557 130.307 76.7267 128.498 78.6846 126.817C80.5969 125.176 82.6161 123.597 84.8838 122.44C87.4134 121.148 90.1093 120.565 92.9424 120.591C94.7112 120.607 96.5088 120.806 98.2725 120.908C100.045 121.011 101.801 121.016 103.53 120.655C106.716 119.99 109.119 117.852 111.634 115.831C112.837 114.865 114.087 113.927 115.479 113.215C116.873 112.502 118.373 112.008 119.907 111.721Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M118.939 114.696C121.915 113.961 125.045 114.028 128.026 114.631C134.134 115.868 139.374 119.413 144.163 123.201L145.114 123.961L145.163 124.01C145.206 124.063 145.229 124.127 145.227 124.196C145.224 124.281 145.183 124.353 145.134 124.402C145.038 124.497 144.854 124.553 144.7 124.428C144.027 123.886 143.35 123.349 142.666 122.822C137.526 118.866 131.762 115.388 125.195 114.856C122.075 114.603 118.869 115.024 116.046 116.394C114.711 117.042 113.516 117.904 112.35 118.826C111.189 119.744 110.045 120.731 108.835 121.605C107.67 122.447 106.415 123.19 105.036 123.658C103.474 124.187 101.822 124.348 100.195 124.347C98.4897 124.346 96.775 124.173 95.083 124.064C93.3856 123.955 91.7009 123.909 90.0254 124.159C85.0241 124.903 81.0462 128.285 77.3545 131.582C73.7584 134.793 70.0554 138.316 65.2939 139.807C65.2042 139.835 65.1138 139.823 65.04 139.775C64.9708 139.73 64.9287 139.661 64.9101 139.594C64.8738 139.462 64.919 139.27 65.1152 139.209C65.7107 139.022 66.2949 138.805 66.8652 138.554C69.1833 137.534 71.2306 136.064 73.1807 134.438C74.1557 133.625 75.1055 132.774 76.0518 131.921C76.9972 131.069 77.9399 130.214 78.8994 129.397C80.7776 127.798 82.7641 126.27 84.9902 125.15C87.4352 123.92 90.0422 123.348 92.7734 123.355C94.4948 123.36 96.2328 123.544 97.9453 123.653C99.6644 123.762 101.369 123.797 103.057 123.509C106.227 122.97 108.647 121.026 111.115 119.016C112.246 118.095 113.402 117.172 114.674 116.422C116.001 115.64 117.446 115.065 118.939 114.696Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M117.547 117.867C120.353 116.907 123.38 116.735 126.298 117.11C132.643 117.926 138.163 121.336 143.108 125.144C143.264 125.264 143.255 125.454 143.189 125.57C143.154 125.63 143.095 125.689 143.013 125.713C142.923 125.739 142.831 125.717 142.757 125.66C142.44 125.416 142.121 125.173 141.801 124.933L140.835 124.221C135.774 120.562 130.014 117.613 123.672 117.552C120.692 117.523 117.696 118.16 115.11 119.652C113.876 120.364 112.758 121.243 111.645 122.148C110.534 123.051 109.427 123.984 108.228 124.791C107.05 125.583 105.776 126.238 104.394 126.617C102.886 127.031 101.317 127.133 99.7745 127.108C98.1287 127.082 96.4672 126.91 94.8311 126.81C93.1884 126.71 91.5576 126.681 89.9395 126.934C85.102 127.692 81.2253 130.896 77.6368 134.09C74.1215 137.218 70.5632 140.666 66.0079 142.331C65.9197 142.363 65.827 142.356 65.75 142.308C65.6783 142.264 65.6365 142.193 65.6182 142.126C65.5826 141.995 65.6261 141.807 65.8145 141.738L66.2413 141.575C66.6665 141.407 67.0854 141.224 67.4961 141.023C69.6995 139.947 71.6568 138.489 73.5323 136.9C74.4701 136.106 75.3857 135.28 76.3018 134.455C77.2171 133.63 78.1331 132.806 79.0674 132.015C80.8944 130.47 82.8287 128.998 84.9874 127.913C87.3499 126.725 89.8749 126.138 92.5166 126.12C94.1578 126.109 95.8022 126.266 97.4288 126.38C99.0598 126.493 100.679 126.563 102.294 126.381C103.861 126.204 105.356 125.744 106.732 124.978C108.008 124.269 109.165 123.363 110.309 122.434C112.535 120.626 114.756 118.822 117.547 117.867Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M116.307 121.116C118.888 119.987 121.732 119.58 124.526 119.718C130.648 120.02 136.156 122.865 141.025 126.353V126.354C141.187 126.471 141.18 126.663 141.113 126.78C141.078 126.84 141.019 126.897 140.938 126.922C140.85 126.949 140.758 126.931 140.682 126.876C140.041 126.417 139.393 125.968 138.736 125.536C133.764 122.271 128.032 119.922 122.013 120.37C119.164 120.582 116.433 121.472 114.068 123.075C112.949 123.833 111.911 124.701 110.854 125.563C109.799 126.424 108.727 127.277 107.55 127.992C105.004 129.54 102.193 129.956 99.2776 129.862C97.7201 129.812 96.1367 129.65 94.5862 129.562C93.0265 129.475 91.4775 129.461 89.9416 129.701C85.2763 130.431 81.4912 133.446 78.0149 136.521C74.5835 139.557 71.1732 142.936 66.843 144.77C66.6621 144.847 66.5006 144.746 66.4328 144.631C66.3981 144.572 66.3765 144.492 66.3957 144.408C66.4167 144.318 66.4803 144.249 66.5666 144.212L66.9582 144.041C67.3472 143.866 67.7302 143.679 68.1067 143.478C70.2098 142.359 72.0886 140.907 73.9006 139.351C74.8067 138.572 75.6944 137.768 76.5852 136.967C77.4753 136.166 78.3689 135.367 79.2824 134.602C81.06 133.111 82.9437 131.7 85.0383 130.654C87.2791 129.536 89.6787 128.945 92.1828 128.891C93.7615 128.857 95.3363 128.989 96.8977 129.103C98.4627 129.218 100.017 129.314 101.572 129.21C103.121 129.107 104.64 128.775 106.035 128.106C107.302 127.5 108.453 126.675 109.564 125.799C111.693 124.118 113.75 122.235 116.307 121.116Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M113.711 125.326C117.345 122.855 121.766 122.1 126.069 122.611C130.355 123.121 134.322 124.83 137.957 127.064L138.679 127.518L138.736 127.563C138.85 127.676 138.84 127.839 138.779 127.943C138.744 128.004 138.685 128.059 138.606 128.085C138.522 128.112 138.431 128.1 138.352 128.05C136.142 126.634 133.815 125.38 131.346 124.499C127.382 123.085 122.97 122.579 118.877 123.649C116.774 124.198 114.884 125.196 113.143 126.499C112.273 127.15 111.44 127.847 110.59 128.535C109.741 129.222 108.877 129.899 107.954 130.502C107.875 130.554 107.784 130.567 107.699 130.539C107.62 130.513 107.562 130.457 107.527 130.397C107.458 130.277 107.454 130.082 107.625 129.971L108.019 129.706C108.933 129.077 109.798 128.379 110.665 127.672C111.653 126.866 112.646 126.05 113.711 125.326Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M91.4673 131.689C91.534 131.698 91.5939 131.729 91.6391 131.779C91.6953 131.841 91.7173 131.92 91.7173 131.989C91.7169 132.126 91.6232 132.297 91.4214 132.31C89.5098 132.433 87.6602 132.895 85.9174 133.691C83.0538 134.998 80.617 136.95 78.2514 139.036C75.8915 141.118 73.5945 143.341 71.0337 145.158C70.957 145.212 70.8657 145.23 70.7788 145.203C70.6978 145.178 70.6388 145.121 70.604 145.061C70.5363 144.944 70.5298 144.751 70.6928 144.636L71.2563 144.227C72.5618 143.259 73.8003 142.201 75.0219 141.12C77.4109 139.006 79.7549 136.773 82.4214 134.965C85.1185 133.136 88.11 131.898 91.3989 131.686L91.4673 131.689Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M115.472 127.038C122.14 123.629 130.074 125.423 136.279 128.845L136.335 128.884C136.386 128.928 136.42 128.985 136.432 129.05C136.448 129.131 136.427 129.209 136.392 129.269C136.357 129.33 136.299 129.384 136.223 129.411C136.143 129.44 136.053 129.434 135.971 129.389C134.1 128.357 132.15 127.473 130.108 126.858C126.84 125.875 123.322 125.522 119.965 126.167C118.286 126.49 116.658 127.063 115.174 127.909C113.528 128.848 112.084 130.089 110.598 131.295C110.444 131.42 110.26 131.364 110.164 131.268C110.115 131.22 110.075 131.148 110.072 131.063C110.068 130.972 110.11 130.888 110.182 130.829L110.183 130.828C111.851 129.475 113.52 128.036 115.472 127.038Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M116.901 129.16C122.24 127.075 128.294 127.955 133.404 130.18C133.49 130.218 133.552 130.288 133.573 130.378C133.592 130.461 133.571 130.54 133.536 130.6C133.468 130.716 133.306 130.816 133.125 130.737C131.67 130.104 130.17 129.589 128.627 129.226C125.943 128.594 123.128 128.402 120.407 128.852C117.587 129.318 115.11 130.523 112.842 132.254C112.767 132.311 112.675 132.332 112.586 132.306C112.504 132.282 112.445 132.224 112.411 132.164C112.344 132.048 112.336 131.858 112.491 131.738V131.737C113.851 130.7 115.292 129.789 116.901 129.16Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M115.223 132.7C119.698 130.333 124.996 130.326 129.779 131.643C129.869 131.668 129.939 131.727 129.976 131.805C130.011 131.879 130.011 131.959 129.993 132.026C129.974 132.094 129.932 132.162 129.865 132.208C129.793 132.256 129.704 132.271 129.614 132.246C124.957 130.963 119.831 130.971 115.52 133.25L115.105 133.477C115.024 133.524 114.933 133.531 114.852 133.503C114.776 133.476 114.717 133.421 114.682 133.36C114.647 133.3 114.628 133.222 114.643 133.142C114.659 133.057 114.711 132.983 114.792 132.936L115.223 132.7Z" fill="black" stroke="black" stroke-width="0.34661"/>
<path d="M124.076 133.521L124.143 133.53C124.208 133.546 124.264 133.58 124.305 133.629C124.357 133.691 124.379 133.767 124.379 133.837C124.379 133.907 124.357 133.984 124.302 134.046C124.259 134.095 124.201 134.128 124.135 134.141L124.067 134.146C122.012 134.094 119.967 134.338 118.01 134.96C117.921 134.988 117.829 134.977 117.755 134.929C117.686 134.884 117.644 134.814 117.626 134.747C117.589 134.614 117.636 134.423 117.831 134.361L118.21 134.245C120.115 133.688 122.097 133.471 124.076 133.521Z" fill="black" stroke="black" stroke-width="0.34661"/>
</svg>

After

Width:  |  Height:  |  Size: 60 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.2 MiB

+37
View File
@@ -0,0 +1,37 @@
import CaseStudy, {
CASE_STUDY_SURFACE_OPTIONS,
} from "../../app/components/cards/CaseStudy";
export default {
title: "Components/Cards/CaseStudy",
component: CaseStudy,
parameters: {
layout: "centered",
docs: {
description: {
component:
"Figma **Card / CaseStudy** ([21993-32352](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21993-32352)). Fixed **305×305** artwork when **`visual`** is omitted: Mutual Aid **SVG** (**`assets/case-study/`**) + raster tiles for neutral/rose (**[22112-871524](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22112-871524)**).",
},
},
},
argTypes: {
surface: {
control: { type: "select" },
options: [...CASE_STUDY_SURFACE_OPTIONS],
},
},
tags: ["autodocs"],
};
export const Lavender = {
args: { surface: "lavender", imageAlt: "Mutual Aid Colorado logo" },
};
export const Neutral = {
args: { surface: "neutral", imageAlt: "Food Not Bombs logo" },
};
export const Rose = {
args: {
surface: "rose",
imageAlt: "Boulder County Street Medics logo",
},
};
+2 -1
View File
@@ -24,7 +24,8 @@ export default {
},
description: {
control: { type: "text" },
description: "The description text displayed in uppercase",
description:
"Body: 12px/16px (X Small); 14px/20px on md<lg only (Card/Icon, Section 22084-859062)",
},
onClick: { action: "clicked" },
},
+93
View File
@@ -0,0 +1,93 @@
import Groups from "../../app/components/sections/Groups";
const vectorIconSrc = [
"/assets/vector/worker-coop.svg",
"/assets/vector/mutual-aid.svg",
"/assets/vector/open-source.svg",
"/assets/vector/dao.svg",
];
const sampleItems = [
{
icon: (
<img
alt=""
aria-hidden
className="inline-block size-9 object-contain"
height={36}
src={vectorIconSrc[0]}
width={36}
/>
),
title: "Worker's cooperatives",
description:
"Employee-owned businesses often need to clarify how power is shared, decisions are made, and how processes operate within their organizations.",
},
{
icon: (
<img
alt=""
aria-hidden
className="inline-block size-9 object-contain"
height={36}
src={vectorIconSrc[1]}
width={36}
/>
),
title: "Mutual aid groups",
description:
"Mutual aid groups must define how resources are shared, decisions are made, and volunteer coordination operates within their organizations.",
},
{
icon: (
<img
alt=""
aria-hidden
className="inline-block size-9 object-contain"
height={36}
src={vectorIconSrc[2]}
width={36}
/>
),
title: "Open source projects",
description:
"Agree to how contributions are managed, technical decisions are made, and community participation operates within their development communities.",
},
{
icon: (
<img
alt=""
aria-hidden
className="inline-block size-9 object-contain"
height={36}
src={vectorIconSrc[3]}
width={36}
/>
),
title: "DAOs",
description:
"Document token-based voting process, proposal patterns, and how community governance operates within their blockchain ecosystems.",
},
];
export default {
title: "Components/Sections/Groups",
component: Groups,
parameters: {
layout: "fullscreen",
docs: {
description: {
component:
"Figma **Section** (**[22084-859062](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22084-859062&m=dev)**) — baseline stacked cards; **md+** `22084-859470` hairline grid. **`cards/Icon`** tiles (body **12px/16px** from **`lg`**).",
},
},
},
tags: ["autodocs"],
};
export const Default = {
args: {
title: "Who is this for?",
items: sampleItems,
},
};
+4 -5
View File
@@ -11,7 +11,7 @@ export default {
A responsive quote section component that displays inspirational governance quotes with author attribution and decorative geometric elements.
## Features
- **Four variants**: compact, standard, extended, and **statement** (Section/Quote yellow band, dual paragraphs)
- **Four variants**: compact, standard, extended, and **statement** (Section/Quote yellow band; two paragraphs below \`lg\`, one paragraph from \`lg\` on /about and /use-cases — [21967-24638](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21967-24638&m=dev))
- **Responsive design**: Adapts across all breakpoints
- **Error handling**: Graceful fallbacks for image loading failures
- **Accessibility**: WCAG 2.1 AA compliant with proper ARIA labels
@@ -39,12 +39,11 @@ A responsive quote section component that displays inspirational governance quot
},
quote: {
control: { type: "text" },
description:
"Main quote / first paragraph (for `statement`, pair with quoteSecondary)",
description: "Main quote — first statement paragraph",
},
quoteSecondary: {
control: { type: "text" },
description: "Second paragraph when `variant` is `statement`",
description: "Second statement paragraph",
},
author: {
control: { type: "text" },
@@ -129,7 +128,7 @@ export const AllVariants = {
},
};
// Statement band (About page / Figma Section/Quote)
// Statement band (Section/Quote — same layout as /about + /use-cases)
export const StatementAbout = {
args: {
variant: "statement",
+33
View File
@@ -0,0 +1,33 @@
import UseCasesOrgs from "../../app/components/sections/UseCasesOrgs";
import CaseStudy from "../../app/components/cards/CaseStudy";
export default {
title: "Components/Sections/UseCasesOrgs",
component: UseCasesOrgs,
parameters: {
layout: "fullscreen",
docs: {
description: {
component:
"Figma **Orgs** ([21993-33687](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21993-33687&m=dev)): **`CaseStudy`** row (**305×305**, **8px** gap, **24px** horizontal / **48px** bottom padding).",
},
},
},
tags: ["autodocs"],
};
export const Default = {
render: () => (
<UseCasesOrgs>
<CaseStudy
surface="lavender"
imageAlt="Mutual Aid Colorado logo"
/>
<CaseStudy surface="neutral" imageAlt="Food Not Bombs logo" />
<CaseStudy
surface="rose"
imageAlt="Boulder County Street Medics logo"
/>
</UseCasesOrgs>
),
};
+43
View File
@@ -0,0 +1,43 @@
import PageHeader from "../../app/components/type/PageHeader";
export default {
title: "Components/Type/PageHeader",
component: PageHeader,
parameters: {
layout: "fullscreen",
docs: {
description: {
component:
"Figma **Type / PageHeader** ([21004-15902](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21004-15902)). Marketing hero: title, body, optional inverse pill CTA.",
},
},
},
tags: ["autodocs"],
};
export const Default = {
args: {
title: "Mutual aid groups should define structure before they need it",
description:
"Many mutual aid groups deprioritize guidelines in favor of immediate action, but setting up a few key agreements early protects the group's mission.",
ctaText: "Create CommunityRule",
ctaHref: "/create",
},
};
export const WithoutCta = {
args: {
title: "Headline only",
description: "Supporting copy without a call to action.",
},
};
/** Use cases header: stacked below `lg`; single line at **`lg`** ([21004-24825](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21004-24825&m=dev)). */
export const UseCasesMinimal = {
args: {
title: ["See how groups use", "CommunityRule"],
headingAlign: "center",
sectionMinimal: true,
singleLineTitleFromLg: true,
},
};
+40
View File
@@ -0,0 +1,40 @@
import TripleStep from "../../app/components/type/TripleStep";
const sampleSteps = [
{
title: "Get your stakeholders together",
body: "If you're just getting started, you might begin with shared values, decision-making plan, and conflict resolution process.",
},
{
title: "Define how your group should operate",
body: "Involving everyone in shaping these agreements through group discussions, workshops, or a tool like CommunityRule can help build collective buy-in.",
},
{
title: "Have a durable impact",
body: "Consider treating guidelines as a living document that evolves with your group's needs.",
},
];
export default {
title: "Components/Type/TripleStep",
component: TripleStep,
parameters: {
layout: "fullscreen",
docs: {
description: {
component:
"Figma **Section / Triple Step** ([22084-859405](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22084-859405&m=dev)); type baseline ([22112-871527](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22112-871527)). Headline uses **Large/Heading** from **md**; steps use **18px/22px** Bricolage medium + **Small/Paragraph** (14/20); outline CTA; **md+** second column shows **`triple-step.svg`**.",
},
},
},
tags: ["autodocs"],
};
export const Default = {
args: {
heading: "Get recommendations that will make organizing easier",
steps: sampleSteps,
ctaText: "Create Rule",
ctaHref: "/create",
},
};
+13
View File
@@ -109,4 +109,17 @@ describe("Icon (behavioral tests)", () => {
const card = screen.getByRole("button");
expect(card).toHaveAttribute("aria-label", "Test Title: Test Description");
});
it("uses article semantics when interactive is false", () => {
render(
<Icon
interactive={false}
icon={<div>Icon</div>}
title="Static Title"
description="Static Description"
/>,
);
expect(screen.getByRole("article")).toBeInTheDocument();
expect(screen.queryByRole("button")).not.toBeInTheDocument();
});
});
+21 -2
View File
@@ -1,8 +1,11 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import RelatedArticles from "../../app/components/sections/RelatedArticles";
import type { BlogPost } from "../../lib/content";
import {
renderWithProviders as render,
screen,
} from "../utils/test-utils";
vi.mock("next/link", () => ({
default: ({
@@ -63,7 +66,7 @@ const mockPosts: BlogPost[] = [
},
];
// Pure presentational; no provider context needed (mocked thumbnail + useIsMobile).
// MessagesProvider required — container uses useMessages().
describe("RelatedArticles", () => {
it("renders without crashing", () => {
render(
@@ -86,4 +89,20 @@ describe("RelatedArticles", () => {
expect(screen.queryByTestId("thumbnail-article-1")).not.toBeInTheDocument();
expect(screen.getByTestId("thumbnail-article-2")).toBeInTheDocument();
});
it("useCases variant shows localized stacked title", () => {
render(
<RelatedArticles
relatedPosts={mockPosts}
currentPostSlug="current"
variant="useCases"
/>,
);
expect(
screen.getByRole("heading", {
level: 2,
name: /Tools to set your group up for success/,
}),
).toBeInTheDocument();
});
});
+31
View File
@@ -0,0 +1,31 @@
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import CaseStudy from "../../../app/components/cards/CaseStudy";
describe("CaseStudy", () => {
it("renders tile container", () => {
const { container } = render(
<CaseStudy surface="lavender" imageAlt="Mutual Aid Colorado logo" />,
);
expect(container.querySelector('[data-figma-node="21993-32352"]')).toBeTruthy();
});
it("renders built-in raster when visual is omitted (neutral)", () => {
render(
<CaseStudy surface="neutral" imageAlt="Food Not Bombs logo" />,
);
expect(
screen.getByRole("img", { name: "Food Not Bombs logo" }),
).toHaveAttribute("src");
});
it("uses Mutual Aid vector on lavender surface", () => {
const { container } = render(
<CaseStudy surface="lavender" imageAlt="Mutual Aid Colorado logo" />,
);
expect(container.querySelector("img")?.getAttribute("src")).toContain(
"case-study-mutual-aid.svg",
);
});
});
+44
View File
@@ -0,0 +1,44 @@
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import Groups from "../../../app/components/sections/Groups";
describe("Groups", () => {
it("renders a static icon tile grid", () => {
const { container } = render(
<Groups
title="Who is this for?"
items={[
{
icon: <span data-testid="ico-a">a</span>,
title: "One",
description: "First description text.",
},
{
icon: <span data-testid="ico-b">b</span>,
title: "Two",
description: "Second description text.",
},
{
icon: <span data-testid="ico-c">c</span>,
title: "Three",
description: "Third description text.",
},
{
icon: <span data-testid="ico-d">d</span>,
title: "Four",
description: "Fourth description text.",
},
]}
/>,
);
expect(
screen.getByRole("heading", { level: 2, name: "Who is this for?" }),
).toBeInTheDocument();
expect(screen.getAllByRole("article")).toHaveLength(4);
expect(screen.queryByRole("button")).not.toBeInTheDocument();
expect(
container.querySelector('[data-figma-node="22085-860411"]'),
).toBeTruthy();
});
});
@@ -0,0 +1,20 @@
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import UseCasesOrgs from "../../../app/components/sections/UseCasesOrgs";
describe("UseCasesOrgs", () => {
it("renders children", () => {
const { container } = render(
<UseCasesOrgs>
<div>Child A</div>
<div>Child B</div>
</UseCasesOrgs>,
);
expect(screen.getByText("Child A")).toBeInTheDocument();
expect(screen.getByText("Child B")).toBeInTheDocument();
expect(
container.querySelector('[data-figma-node="21993-33687"]'),
).toBeTruthy();
});
});
+72
View File
@@ -0,0 +1,72 @@
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import PageHeader from "../../../app/components/type/PageHeader";
describe("PageHeader", () => {
it("renders title and description", () => {
render(
<PageHeader
title="Test title"
description="Test description body."
ctaText="Go"
ctaHref="/create"
/>,
);
expect(
screen.getByRole("heading", { level: 1, name: "Test title" }),
).toBeInTheDocument();
expect(screen.getByText("Test description body.")).toBeInTheDocument();
expect(screen.getByRole("link", { name: "Go" })).toHaveAttribute(
"href",
"/create",
);
});
it("omits CTA when ctaText is absent", () => {
render(
<PageHeader title="Only title" description="Only description" />,
);
expect(screen.queryByRole("link")).not.toBeInTheDocument();
});
it("omits description when omitted and renders stacked centered title lines", () => {
render(
<PageHeader
title={["See how groups use", "CommunityRule"]}
headingAlign="center"
sectionMinimal
/>,
);
const heading = screen.getByRole("heading", { level: 1 });
expect(heading).toHaveTextContent(/See how groups useCommunityRule/);
expect(
heading.querySelectorAll("span.block"),
).toHaveLength(2);
expect(screen.queryByRole("paragraph")).not.toBeInTheDocument();
expect(screen.queryByRole("link")).not.toBeInTheDocument();
});
it("renders use-cases lg single-line title segments when singleLineTitleFromLg", () => {
render(
<PageHeader
title={["See how groups use", "CommunityRule"]}
headingAlign="center"
sectionMinimal
singleLineTitleFromLg
/>,
);
const heading = screen.getByRole("heading", { level: 1 });
expect(heading).toHaveTextContent(/See how groups use CommunityRule/);
expect(
heading.querySelectorAll("span.block.lg\\:inline"),
).toHaveLength(2);
expect(heading.closest("section")).toHaveAttribute(
"data-figma-node",
"21004-24825",
);
});
});
+32
View File
@@ -0,0 +1,32 @@
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import TripleStep from "../../../app/components/type/TripleStep";
describe("TripleStep", () => {
it("renders heading, steps, and CTA", () => {
render(
<TripleStep
heading="Get organized"
steps={[
{ title: "Step one", body: "Body one." },
{ title: "Step two", body: "Body two." },
{ title: "Step three", body: "Body three." },
]}
ctaText="Create Rule"
ctaHref="/create"
/>,
);
expect(
screen.getByRole("heading", { level: 2, name: "Get organized" }),
).toBeInTheDocument();
expect(
document.querySelector('[data-figma-node="22084-859405"]'),
).toBeTruthy();
expect(screen.getByText("Step one")).toBeInTheDocument();
expect(screen.getByRole("link", { name: "Create Rule" })).toHaveAttribute(
"href",
"/create",
);
});
});
@@ -48,4 +48,45 @@ describe("TripleTextBlock", () => {
);
expect(screen.getByText("Only body.")).toBeInTheDocument();
});
it("useCases preset renders persistent section heading, column h3 titles, dual paragraphs, outline CTA", () => {
const { container } = render(
<TripleTextBlock
layoutPreset="useCases"
title="Why Horizontal groups need CommunityRule"
ctaText="Setup your community"
ctaHref="/create"
columns={[
{
title: "Share Leadership",
description: "First paragraph.",
descriptionSecondary: "Second paragraph.",
},
]}
/>,
);
expect(
container.querySelector('[data-figma-node="22085-860414"]'),
).toBeTruthy();
expect(
screen.getByRole("heading", {
level: 2,
name: "Why Horizontal groups need CommunityRule",
}),
).toBeInTheDocument();
expect(
screen.getByRole("heading", {
level: 3,
name: "Share Leadership",
}),
).toBeInTheDocument();
expect(screen.getByText("First paragraph.")).toBeInTheDocument();
expect(screen.getByText("Second paragraph.")).toBeInTheDocument();
expect(screen.getByRole("link", { name: "Setup your community" })).toHaveAttribute(
"href",
"/create",
);
});
});
+5 -1
View File
@@ -223,7 +223,7 @@ describe("QuoteBlock Component", () => {
).not.toBeInTheDocument();
});
test("statement variant renders dual paragraphs without attribution", () => {
test("statement variant uses one paragraph with responsive stack (Figma 21967-24638)", () => {
render(
<QuoteBlock
variant="statement"
@@ -237,10 +237,14 @@ describe("QuoteBlock Component", () => {
name: /first paragraph of the statement/i,
});
expect(region).toBeInTheDocument();
expect(region).toHaveAttribute("data-figma-node", "21967-24638");
expect(
screen.getByText("Second paragraph of the statement."),
).toBeInTheDocument();
expect(screen.queryByRole("cite")).not.toBeInTheDocument();
const heading = region.querySelector("#about-test-quote-content");
expect(heading?.querySelectorAll("span.block.lg\\:inline").length).toBe(2);
});
test("statement variant logs when quoteSecondary is missing", () => {
+26
View File
@@ -74,6 +74,25 @@ describe("RuleStack Component", () => {
expect(fetchMock.mock.calls.length).toBe(callsBefore);
});
test("uses translationNamespace for section heading copy", () => {
render(
<RuleStack
initialGridEntries={homeFeatured}
translationNamespace="pages.useCases.ruleStack"
/>,
);
const heading = screen.getByRole("heading", { level: 2 });
expect(heading.textContent).toMatch(
/Get Templates that help your community run smoothly/,
);
});
test("defaults to home rule stack heading copy when namespace omitted", () => {
render(<RuleStack initialGridEntries={homeFeatured} />);
const heading = screen.getByRole("heading", { level: 2 });
expect(heading.textContent).toMatch(/Popular templates/);
});
test("renders four featured governance template cards on the home row", async () => {
render(<RuleStack />);
await waitForRuleStackCards();
@@ -166,6 +185,7 @@ describe("RuleStack Component", () => {
await waitForRuleStackCards();
const section = document.querySelector("section");
expect(section).toHaveAttribute("data-figma-node", "22085-860413");
expect(section).toHaveClass("px-[20px]", "py-[32px]");
expect(section?.className).toMatch(/min-\[640px\]:px-\[32px\]/);
expect(section?.className).toMatch(/min-\[640px\]:py-\[48px\]/);
@@ -281,6 +301,12 @@ describe("RuleStack Component", () => {
expect(circlesIcon?.className).toMatch(
/min-\[640px\]:max-\[1023px\]:h-\[56px\]/,
);
expect(circlesIcon?.className).toMatch(
/min-\[1024px\]:max-\[1439px\]:w-\[90px\]/,
);
expect(circlesIcon?.className).toMatch(
/min-\[1024px\]:max-\[1439px\]:h-\[90px\]/,
);
expect(circlesIcon?.className).toMatch(/min-\[1440px\]:w-\[90px\]/);
expect(circlesIcon?.className).toMatch(/min-\[1440px\]:h-\[90px\]/);
});