diff --git a/app/components/AskOrganizer.js b/app/components/AskOrganizer.js new file mode 100644 index 0000000..0e3244d --- /dev/null +++ b/app/components/AskOrganizer.js @@ -0,0 +1,106 @@ +"use client"; + +import React from "react"; +import ContentLockup from "./ContentLockup"; +import Button from "./Button"; + +const AskOrganizer = ({ + title, + subtitle, + description, + buttonText = "Ask an organizer", + buttonHref = "#", + className = "", + variant = "centered", // centered, left-aligned, compact + onContactClick, // Analytics callback +}) => { + // Analytics tracking for contact button clicks + const handleContactClick = (event) => { + // Track contact button interaction + if (onContactClick) { + onContactClick({ + event: "contact_button_click", + component: "AskOrganizer", + variant, + buttonText, + buttonHref, + timestamp: new Date().toISOString(), + }); + } + + // Additional analytics tracking (can be expanded) + if (typeof window !== "undefined" && window.gtag) { + window.gtag("event", "contact_button_click", { + event_category: "engagement", + event_label: "ask_organizer", + value: 1, + }); + } + }; + + // Variant-specific styling + const variantStyles = { + centered: { + container: "text-center", + buttonContainer: "flex justify-center", + }, + "left-aligned": { + container: "text-left", + buttonContainer: "flex justify-start", + }, + compact: { + container: "text-center", + buttonContainer: "flex justify-center", + }, + }; + + const styles = variantStyles[variant] || variantStyles.centered; + + // Section padding based on variant + const sectionPadding = + variant === "compact" + ? "py-[var(--spacing-scale-016)] px-[var(--spacing-scale-016)] md:py-[var(--spacing-scale-032)] md:px-[var(--spacing-scale-032)]" + : "py-[var(--spacing-scale-032)] px-[var(--spacing-scale-032)] md:py-[var(--spacing-scale-096)] md:px-[var(--spacing-scale-064)]"; + + // Gap between content and button based on variant + const contentGap = + variant === "compact" + ? "gap-[var(--spacing-scale-020)]" + : "gap-[var(--spacing-scale-040)]"; + + return ( +
+
+ {/* Content Lockup */} + + + {/* Button */} +
+ +
+
+
+ ); +}; + +export default AskOrganizer; diff --git a/app/components/ContentLockup.js b/app/components/ContentLockup.js index b544105..f6056c9 100644 --- a/app/components/ContentLockup.js +++ b/app/components/ContentLockup.js @@ -9,58 +9,137 @@ const ContentLockup = ({ ctaText, ctaHref, buttonClassName = "", + variant = "hero", + linkText, + linkHref, + alignment = "center", // center, left }) => { + // Variant-specific styling + const variantStyles = { + hero: { + container: + "flex flex-col gap-[var(--spacing-scale-006)] sm:gap-[var(--spacing-scale-012)] md:gap-[var(--spacing-scale-020)] lg:gap-[var(--spacing-scale-020)] relative z-10", + textContainer: + "flex flex-col md:gap-[var(--spacing-scale-004)] lg:gap-[var(--spacing-scale-008)] xl:gap-[var(--spacing-scale-020)]", + titleGroup: "flex flex-col xl:gap-0", + titleContainer: + "flex gap-[var(--spacing-scale-008)] xl:gap-[var(--spacing-scale-010)] items-center", + title: + "font-bricolage-grotesque font-medium text-[32px] leading-[32px] sm:text-[52px] sm:leading-[52px] md:text-[44px] md:leading-[44px] lg:text-[64px] lg:leading-[64px] xl:text-[96px] xl:leading-[110%] text-[var(--color-content-inverse-primary)]", + subtitle: + "font-bricolage-grotesque font-medium text-[32px] leading-[32px] sm:text-[52px] sm:leading-[52px] md:text-[44px] md:leading-[44px] lg:text-[64px] lg:leading-[64px] xl:text-[96px] xl:leading-[110%] text-[var(--color-content-inverse-primary)]", + description: + "font-inter font-normal text-[18px] leading-[130%] lg:text-[24px] lg:leading-[32px] xl:text-[32px] xl:leading-[40px] text-[var(--color-content-inverse-primary)]", + shape: + "w-[27.2px] h-[27.2px] md:w-[34px] md:h-[34px] lg:w-[50px] lg:h-[50px]", + }, + feature: { + container: "flex flex-col gap-[var(--spacing-scale-012)] relative z-10", + textContainer: "flex flex-col gap-[var(--spacing-scale-012)]", + titleGroup: "flex flex-col gap-[var(--spacing-scale-012)]", + titleContainer: "flex gap-[var(--spacing-scale-008)] items-center", + title: + "font-bricolage-grotesque font-medium text-[32px] leading-[130%] tracking-[0] text-[var(--color-content-default-primary)]", + subtitle: + "font-space-grotesk font-normal text-[20px] leading-[130%] tracking-[0] text-[var(--color-content-default-primary)]", + description: + "font-inter font-normal text-[16px] leading-[140%] lg:text-[18px] lg:leading-[150%] xl:text-[20px] xl:leading-[160%] text-[var(--color-content-secondary)]", + shape: + "w-[20px] h-[20px] md:w-[24px] md:h-[24px] lg:w-[28px] lg:h-[28px]", + }, + ask: { + container: "flex flex-col gap-[var(--spacing-scale-008)] relative z-10", + textContainer: "flex flex-col gap-[var(--spacing-scale-008)]", + 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-[44px] md:leading-[110%] xl:text-[52px] xl:leading-[110%] 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: + "w-[16px] h-[16px] md:w-[20px] md:h-[20px] lg:w-[24px] lg:h-[24px]", + }, + }; + + const styles = variantStyles[variant] || variantStyles.hero; + return ( -
- {/* Text content container */} -
- {/* Title and subtitle group - no gap between them at xl */} -
- {/* Title container */} -
-

- {title} -

- Decorative shapes +
+ {variant === "ask" ? ( + /* Simplified structure for ask variant */ +
+
+

{title}

+
+

{subtitle}

+
+ ) : ( + /* Full structure for other variants */ +
+ {/* Title and subtitle group */} +
+ {/* Title container */} +
+

{title}

+ {variant === "hero" && ( + Decorative shapes + )} +
+ + {/* Subtitle */} +

{subtitle}

- {/* Subtitle */} -

- {subtitle} -

+ {/* Description */} + {description &&

{description}

}
+ )} - {/* Description - 20px gap from subtitle at xl */} -

- {description} -

-
+ {/* Link for feature variant */} + {variant === "feature" && linkText && ( + + {linkText} + + )} {/* CTA Button */} -
- {/* Small button for xsm and sm breakpoints */} -
- + {ctaText && ( +
+ {/* Small button for xsm and sm breakpoints */} +
+ +
+ {/* Large button for md and lg breakpoints */} +
+ +
+ {/* XLarge button for xl breakpoint */} +
+ +
- {/* Large button for md and lg breakpoints */} -
- -
- {/* XLarge button for xl breakpoint */} -
- -
-
+ )}
); }; diff --git a/app/components/FeatureGrid.js b/app/components/FeatureGrid.js new file mode 100644 index 0000000..ecda70e --- /dev/null +++ b/app/components/FeatureGrid.js @@ -0,0 +1,74 @@ +"use client"; + +import React from "react"; +import ContentLockup from "./ContentLockup"; +import MiniCard from "./MiniCard"; +import Image from "next/image"; + +const FeatureGrid = ({ title, subtitle, className = "" }) => { + return ( +
+
+
+ {/* Feature Content Lockup */} +
+ +
+ + {/* MiniCard Grid */} +
+ + + + +
+
+
+
+ ); +}; + +export default FeatureGrid; diff --git a/app/components/HeroDecor.js b/app/components/HeroDecor.js index ab12715..8ce8adc 100644 --- a/app/components/HeroDecor.js +++ b/app/components/HeroDecor.js @@ -23,9 +23,9 @@ const HeroDecor = ({ className = "" }) => { {/* 1) make noise */} @@ -35,10 +35,10 @@ const HeroDecor = ({ className = "" }) => { result="softNoise" type="matrix" values=" - 0 0 0 0 0 - 0 0 0 0 0 - 0 0 0 0 0 - 0 0 0 0.15 0" + 0.8 0 0 0 0.3 + 0 0.6 0 0 0.2 + 0 0 1.0 0 0.4 + 0 0 0 0.25 0" /> {/* 3) MASK noise to the element's alpha only */} { + const cardContent = ( +
+ {/* Top part - Inner panel */} +
+ {/* Content for the inner panel */} + {panelContent && ( +
+ { +
+ )} + {children} +
+ + {/* Bottom part - Text container */} +
+ {labelLine1 && labelLine2 ? ( + <> +
{labelLine1}
+
{labelLine2}
+
 
+ + ) : ( + label + )} +
+
+ ); + + // If href is provided, render as a link + if (href) { + return ( + + {cardContent} + + ); + } + + // If onClick is provided, render as a button + if (onClick) { + return ( + + ); + } + + // Default render as a div + return ( +
+ {cardContent} +
+ ); +}; + +export default MiniCard; diff --git a/app/components/QuoteBlock.js b/app/components/QuoteBlock.js index 3598a2f..037e5c4 100644 --- a/app/components/QuoteBlock.js +++ b/app/components/QuoteBlock.js @@ -126,6 +126,15 @@ const QuoteBlock = ({
+ {/* Background with noise texture */} +
#grain\')', + }} + /> + {/* DECORATIONS (behind content) */} {config.showDecor && ( + +
); } diff --git a/public/assets/Feature_Exercises.png b/public/assets/Feature_Exercises.png new file mode 100644 index 0000000..1af6a6c Binary files /dev/null and b/public/assets/Feature_Exercises.png differ diff --git a/public/assets/Feature_Guidance.png b/public/assets/Feature_Guidance.png new file mode 100644 index 0000000..972e5e5 Binary files /dev/null and b/public/assets/Feature_Guidance.png differ diff --git a/public/assets/Feature_Support.png b/public/assets/Feature_Support.png new file mode 100644 index 0000000..9ddcdc2 Binary files /dev/null and b/public/assets/Feature_Support.png differ diff --git a/public/assets/Feature_Support.svg b/public/assets/Feature_Support.svg new file mode 100644 index 0000000..185289b --- /dev/null +++ b/public/assets/Feature_Support.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/Feature_Tools.png b/public/assets/Feature_Tools.png new file mode 100644 index 0000000..9fa79b2 Binary files /dev/null and b/public/assets/Feature_Tools.png differ diff --git a/public/file.svg b/public/file.svg deleted file mode 100644 index 004145c..0000000 --- a/public/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/globe.svg b/public/globe.svg deleted file mode 100644 index 567f17b..0000000 --- a/public/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index 7705396..0000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/window.svg b/public/window.svg deleted file mode 100644 index b2b2a44..0000000 --- a/public/window.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/stories/AskOrganizer.stories.js b/stories/AskOrganizer.stories.js new file mode 100644 index 0000000..7dc0e03 --- /dev/null +++ b/stories/AskOrganizer.stories.js @@ -0,0 +1,78 @@ +import AskOrganizer from "../app/components/AskOrganizer"; + +export default { + title: "Components/AskOrganizer", + component: AskOrganizer, + parameters: { + docs: { + description: { + component: + "The AskOrganizer component provides clear pathways for user inquiries. This component serves as a conversion point throughout the platform.", + }, + }, + }, + argTypes: { + title: { + control: "text", + description: "The main title for the ask organizer section", + }, + subtitle: { + control: "text", + description: "The subtitle text", + }, + description: { + control: "text", + description: "Additional description text", + }, + buttonText: { + control: "text", + description: "Text for the call-to-action button", + }, + buttonHref: { + control: "text", + description: "URL for the button link", + }, + variant: { + control: { type: "select" }, + options: ["centered", "left-aligned", "compact"], + description: "Layout variant for the component", + }, + onContactClick: { + action: "contact clicked", + description: "Analytics callback for contact button clicks", + }, + }, +}; + +export const Default = { + args: { + title: "Still have questions?", + subtitle: "Get answers from an experienced organizer", + buttonText: "Ask an organizer", + buttonHref: "#contact", + variant: "centered", + onContactClick: (data) => console.log("Contact clicked:", data), + }, +}; + +export const LeftAligned = { + args: { + title: "Still have questions?", + subtitle: "Get answers from an experienced organizer", + buttonText: "Ask an organizer", + buttonHref: "#contact", + variant: "left-aligned", + onContactClick: (data) => console.log("Contact clicked:", data), + }, +}; + +export const Compact = { + args: { + title: "Still have questions?", + subtitle: "Get answers from an experienced organizer", + buttonText: "Ask an organizer", + buttonHref: "#contact", + variant: "compact", + onContactClick: (data) => console.log("Contact clicked:", data), + }, +}; diff --git a/stories/ContentLockup.stories.js b/stories/ContentLockup.stories.js index 6247073..387d4ad 100644 --- a/stories/ContentLockup.stories.js +++ b/stories/ContentLockup.stories.js @@ -5,44 +5,24 @@ export default { component: ContentLockup, parameters: { layout: "centered", - docs: { - description: { - component: - "A content lockup component that groups title, subtitle, description, and CTA button. Features responsive typography and spacing that adapts across breakpoints. Used within the HeroBanner component.", - }, - }, }, argTypes: { - title: { - control: { type: "text" }, - description: "The main title text", - }, - subtitle: { - control: { type: "text" }, - description: "The subtitle text", - }, - description: { - control: { type: "text" }, - description: "The description text", - }, - ctaText: { - control: { type: "text" }, - description: "The call-to-action button text", - }, - ctaHref: { - control: { type: "text" }, - description: "The call-to-action button link", - }, - buttonClassName: { - control: { type: "text" }, - description: - "Additional CSS classes to apply to the large button (md/lg breakpoints)", + title: { control: { type: "text" } }, + subtitle: { control: { type: "text" } }, + description: { control: { type: "text" } }, + ctaText: { control: { type: "text" } }, + ctaHref: { control: { type: "text" } }, + buttonClassName: { control: { type: "text" } }, + variant: { + control: { type: "select" }, + options: ["hero", "feature", "ask"], }, + linkText: { control: { type: "text" } }, + linkHref: { control: { type: "text" } }, }, - tags: ["autodocs"], }; -export const Default = { +export const Hero = { args: { title: "Collaborate", subtitle: "with clarity", @@ -50,68 +30,37 @@ export const Default = { "Help your community make important decisions in a way that reflects its unique values.", ctaText: "Learn how Community Rule works", ctaHref: "#", - }, - parameters: { - docs: { - description: { - story: "Default content lockup with standard Community Rule messaging.", - }, - }, + variant: "hero", }, }; -export const LongDescription = { +export const Feature = { args: { - title: "Collaborate", - subtitle: "with clarity", + title: "Build", + subtitle: "consensus", description: - "Help your community make important decisions in a way that reflects its unique values. Our platform provides the tools and frameworks needed to build successful, sustainable communities that can navigate complex challenges together.", - ctaText: "Learn how Community Rule works", + "Create structured decision-making processes that help your community reach agreement on important matters.", + ctaText: "Explore consensus methods", ctaHref: "#", - }, - parameters: { - docs: { - description: { - story: - "Content lockup with longer description text to test text wrapping.", - }, - }, + variant: "feature", }, }; -export const ShortContent = { +export const FeatureWithLink = { args: { - title: "Simple", - subtitle: "solution", - description: "Easy community decision making.", - ctaText: "Try it", - ctaHref: "#", - }, - parameters: { - docs: { - description: { - story: "Content lockup with minimal content to test compact layouts.", - }, - }, + title: "We've got your back, every step of the way", + subtitle: + "Use our toolkit to improve, document, and evolve your organization.", + variant: "feature", + linkText: "Learn more", + linkHref: "#", }, }; -export const CustomButtonStyling = { +export const Ask = { args: { - title: "Collaborate", - subtitle: "with clarity", - description: - "Help your community make important decisions in a way that reflects its unique values.", - ctaText: "Learn how Community Rule works", - ctaHref: "#", - buttonClassName: "shrink-0 whitespace-nowrap min-w-[280px]", - }, - parameters: { - docs: { - description: { - story: - "Content lockup with custom button styling applied to the large button (md/lg breakpoints).", - }, - }, + title: "Still have questions?", + subtitle: "Get answers from an experienced organizer", + variant: "ask", }, }; diff --git a/stories/FeatureGrid.stories.js b/stories/FeatureGrid.stories.js new file mode 100644 index 0000000..28864ba --- /dev/null +++ b/stories/FeatureGrid.stories.js @@ -0,0 +1,81 @@ +import FeatureGrid from "../app/components/FeatureGrid"; + +export default { + title: "Components/FeatureGrid", + component: FeatureGrid, + parameters: { + layout: "fullscreen", + docs: { + description: { + component: ` +A responsive feature grid component that displays organizational tools and services in a clean card-based layout with supportive messaging and categorized feature highlights. + +## Features + +- **Responsive Layout**: Adapts from 2x2 grid on mobile to 1x4 grid on tablet to horizontal layout on desktop +- **ContentLockup Integration**: Uses the feature variant with "Learn more" link +- **MiniCard Grid**: Four feature cards with color-coded backgrounds and icons +- **Accessibility**: Full keyboard navigation, focus indicators, and ARIA labels +- **Design System**: Uses design tokens for consistent spacing, colors, and typography + +## Responsive Behavior + +- **Mobile (< 768px)**: 2x2 grid layout with ContentLockup on top +- **Tablet (768px - 1024px)**: 1x4 grid layout with ContentLockup on top +- **Desktop (> 1024px)**: Horizontal layout with ContentLockup on left, 1x4 grid on right + +## Interactive Elements + +- **MiniCards**: Hover effects, focus indicators, and keyboard navigation +- **Learn More Link**: Underlined link with focus states +- **Color-coded Features**: Royal, green, pink, and blue backgrounds for categorization + +## Accessibility + +- WCAG 2.1 AA compliant +- Keyboard navigation support +- Screen reader friendly with proper ARIA labels +- Focus management with visible indicators + `, + }, + }, + }, + argTypes: { + title: { + control: { type: "text" }, + description: "Main headline text for the ContentLockup", + }, + subtitle: { + control: { type: "text" }, + description: "Supporting subtitle text for the ContentLockup", + }, + className: { + control: { type: "text" }, + description: "Additional CSS classes for custom styling", + }, + }, +}; + +export const Default = { + args: { + title: "We've got your back, every step of the way", + subtitle: + "Use our toolkit to improve, document, and evolve your organization.", + }, + parameters: { + docs: { + description: { + story: ` +Default FeatureGrid with standard content. This component demonstrates: + +- **ContentLockup**: Feature variant with title, subtitle, and "Learn more" link +- **MiniCard Grid**: Four feature cards with different colors and icons +- **Responsive Design**: Layout adapts across mobile, tablet, and desktop breakpoints +- **Interactive States**: Hover effects and focus indicators on all interactive elements + +The component uses a dark background (#171717) with rounded corners and proper spacing using design tokens. + `, + }, + }, + }, +}; diff --git a/stories/MiniCard.stories.js b/stories/MiniCard.stories.js new file mode 100644 index 0000000..77e6997 --- /dev/null +++ b/stories/MiniCard.stories.js @@ -0,0 +1,77 @@ +import MiniCard from "../app/components/MiniCard"; + +export default { + title: "Components/MiniCard", + component: MiniCard, + parameters: { + layout: "centered", + }, + argTypes: { + backgroundColor: { + control: "select", + options: [ + "bg-[var(--color-surface-default-brand-royal)]", + "bg-[#D1FFE2]", + "bg-[#F4CAFF]", + "bg-[#CBDDFF]", + ], + }, + labelLine1: { control: "text" }, + labelLine2: { control: "text" }, + panelContent: { control: "text" }, + href: { control: "text" }, + onClick: { action: "clicked" }, + ariaLabel: { control: "text" }, + }, +}; + +export const Default = { + args: { + backgroundColor: "bg-[var(--color-surface-default-brand-royal)]", + labelLine1: "Decision-making", + labelLine2: "support", + panelContent: "assets/Feature_Support.png", + }, +}; + +export const ColorVariants = { + render: () => ( +
+ + + + +
+ ), +}; + +export const AsLink = { + args: { + backgroundColor: "bg-[var(--color-surface-default-brand-royal)]", + labelLine1: "Decision-making", + labelLine2: "support", + panelContent: "assets/Feature_Support.png", + href: "#decision-making", + ariaLabel: "Navigate to decision-making support tools", + }, +};