Issue #59 fixes
@@ -126,7 +126,7 @@ export default async function BlogPostPage({ params }: PageProps) {
|
|||||||
headline: post.frontmatter.title,
|
headline: post.frontmatter.title,
|
||||||
description: post.frontmatter.description,
|
description: post.frontmatter.description,
|
||||||
author: {
|
author: {
|
||||||
"@type": "Person",
|
"@type": "Organization",
|
||||||
name: post.frontmatter.author,
|
name: post.frontmatter.author,
|
||||||
},
|
},
|
||||||
publisher: {
|
publisher: {
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ function ChipView({
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
|
if (e.target instanceof HTMLInputElement) return;
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleClick(e as unknown as React.MouseEvent<HTMLButtonElement>);
|
handleClick(e as unknown as React.MouseEvent<HTMLButtonElement>);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const Footer = memo(() => {
|
|||||||
/** Figma 18411:62925 (1024+): org name is one line, `w-full whitespace-nowrap`. */
|
/** Figma 18411:62925 (1024+): org name is one line, `w-full whitespace-nowrap`. */
|
||||||
const orgNameClass = `${bodyTextClass} lg:whitespace-nowrap`;
|
const orgNameClass = `${bodyTextClass} lg:whitespace-nowrap`;
|
||||||
|
|
||||||
const primaryLinkClass = `inline-flex w-fit max-w-full self-start text-[var(--color-content-default-primary)] font-inter text-base font-medium leading-5 tracking-[0%] ${linkFocusClass} p-2 -m-2 cursor-pointer lg:text-2xl lg:font-normal lg:leading-7`;
|
const primaryLinkClass = `inline-flex w-fit max-w-full shrink-0 whitespace-nowrap self-start md:self-end text-[var(--color-content-default-primary)] font-inter text-base font-medium leading-5 tracking-[0%] ${linkFocusClass} p-2 -m-2 cursor-pointer lg:text-2xl lg:font-normal lg:leading-7`;
|
||||||
|
|
||||||
/** Figma 18411:62944: 40px gaps, w-[396px] link block; `p-2` on links overruns 396px—tighten x at `md+` row. */
|
/** Figma 18411:62944: 40px gaps, w-[396px] link block; `p-2` on links overruns 396px—tighten x at `md+` row. */
|
||||||
const legalLinkClass = `inline-flex w-fit max-w-full self-start text-[var(--color-content-default-secondary)] font-inter text-sm font-normal leading-5 tracking-[0%] ${linkFocusClass} p-2 -m-2 cursor-pointer underline decoration-solid [text-decoration-skip-ink:none] md:self-auto md:px-0 md:py-1 md:mx-0 md:text-xs md:leading-4 md:whitespace-nowrap md:no-underline md:text-[var(--color-content-default-primary)] lg:text-sm lg:leading-5 lg:text-[var(--color-content-default-primary)]`;
|
const legalLinkClass = `inline-flex w-fit max-w-full self-start text-[var(--color-content-default-secondary)] font-inter text-sm font-normal leading-5 tracking-[0%] ${linkFocusClass} p-2 -m-2 cursor-pointer underline decoration-solid [text-decoration-skip-ink:none] md:self-auto md:px-0 md:py-1 md:mx-0 md:text-xs md:leading-4 md:whitespace-nowrap md:no-underline md:text-[var(--color-content-default-primary)] lg:text-sm lg:leading-5 lg:text-[var(--color-content-default-primary)]`;
|
||||||
@@ -37,7 +37,11 @@ const Footer = memo(() => {
|
|||||||
name: t("organization.name"),
|
name: t("organization.name"),
|
||||||
email: t("organization.email"),
|
email: t("organization.email"),
|
||||||
url: t("organization.url"),
|
url: t("organization.url"),
|
||||||
sameAs: [t("social.bluesky.url"), t("social.gitlab.url")],
|
sameAs: [
|
||||||
|
t("social.bluesky.url"),
|
||||||
|
t("social.gitea.url"),
|
||||||
|
t("social.mastodon.url"),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -104,27 +108,42 @@ const Footer = memo(() => {
|
|||||||
{/* eslint-disable-next-line @next/next/no-img-element -- social logo */}
|
{/* eslint-disable-next-line @next/next/no-img-element -- social logo */}
|
||||||
<img
|
<img
|
||||||
src={getAssetPath(ASSETS.BLUESKY_LOGO)}
|
src={getAssetPath(ASSETS.BLUESKY_LOGO)}
|
||||||
alt="Bluesky"
|
alt=""
|
||||||
width={24}
|
width={24}
|
||||||
height={22}
|
height={22}
|
||||||
className="h-[21px] w-[24px] flex-shrink-0 transition-transform group-hover:scale-110"
|
className="h-[21px] w-[24px] flex-shrink-0 transition-transform group-hover:scale-110"
|
||||||
/>
|
/>
|
||||||
<div className={bodyTextClass}>{t("social.bluesky.handle")}</div>
|
<div className={bodyTextClass}>{t("social.bluesky.label")}</div>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={t("social.gitlab.url")}
|
href={t("social.gitea.url")}
|
||||||
className={`group inline-flex w-fit max-w-full items-center gap-[var(--spacing-measures-spacing-06,6px)] ${linkFocusClass} p-2 -m-2 cursor-pointer`}
|
className={`group inline-flex w-fit max-w-full items-center gap-[var(--spacing-measures-spacing-06,6px)] ${linkFocusClass} p-2 -m-2 cursor-pointer`}
|
||||||
aria-label={t("social.gitlab.ariaLabel")}
|
aria-label={t("social.gitea.ariaLabel")}
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element -- social icon */}
|
{/* eslint-disable-next-line @next/next/no-img-element -- social icon */}
|
||||||
<img
|
<img
|
||||||
src={getAssetPath(ASSETS.GITLAB_ICON)}
|
src={getAssetPath(ASSETS.GITEA_ICON)}
|
||||||
alt="GitLab"
|
alt=""
|
||||||
width={22}
|
width={22}
|
||||||
height={22}
|
height={22}
|
||||||
className="h-5 w-[22px] flex-shrink-0 grayscale transition-transform group-hover:scale-110"
|
className="h-5 w-[22px] flex-shrink-0 grayscale transition-transform group-hover:scale-110"
|
||||||
/>
|
/>
|
||||||
<div className={bodyTextClass}>{t("social.gitlab.handle")}</div>
|
<div className={bodyTextClass}>{t("social.gitea.label")}</div>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={t("social.mastodon.url")}
|
||||||
|
className={`group inline-flex w-fit max-w-full items-center gap-[var(--spacing-measures-spacing-06,6px)] ${linkFocusClass} p-2 -m-2 cursor-pointer`}
|
||||||
|
aria-label={t("social.mastodon.ariaLabel")}
|
||||||
|
>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element -- social icon */}
|
||||||
|
<img
|
||||||
|
src={getAssetPath(ASSETS.MASTODON_LOGO)}
|
||||||
|
alt=""
|
||||||
|
width={22}
|
||||||
|
height={22}
|
||||||
|
className="h-5 w-[22px] flex-shrink-0 grayscale transition-transform group-hover:scale-110"
|
||||||
|
/>
|
||||||
|
<div className={bodyTextClass}>{t("social.mastodon.label")}</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -70,15 +70,15 @@ const MenuItemContainer = memo<MenuItemProps>(
|
|||||||
"border border-[var(--color-border-default-brand-primary,#fdfaa8)] text-[var(--color-content-default-brand-primary,#fefcc9)] bg-transparent hover:bg-[var(--color-gray-800)]",
|
"border border-[var(--color-border-default-brand-primary,#fdfaa8)] text-[var(--color-content-default-brand-primary,#fefcc9)] bg-transparent hover:bg-[var(--color-gray-800)]",
|
||||||
};
|
};
|
||||||
|
|
||||||
// State styles for Inverse mode (black text on yellow background)
|
// State styles for Inverse mode (black text on yellow HeaderTab / inverse surfaces)
|
||||||
const inverseModeStyles: Record<"default" | "hover" | "selected", string> =
|
const inverseModeStyles: Record<"default" | "hover" | "selected", string> =
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
"bg-transparent text-[var(--color-content-inverse-primary,black)] hover:bg-[var(--color-surface-brand-accent,#4d4a00)] hover:text-[var(--color-content-inverse-primary,black)]",
|
"bg-transparent text-[var(--color-content-inverse-primary,black)] hover:bg-[var(--color-surface-inverse-brand-secondary)] hover:text-[var(--color-content-inverse-primary,black)]",
|
||||||
hover:
|
hover:
|
||||||
"bg-[var(--color-surface-brand-accent,#4d4a00)] text-[var(--color-content-inverse-primary,black)]",
|
"bg-[var(--color-surface-inverse-brand-secondary)] text-[var(--color-content-inverse-primary,black)]",
|
||||||
selected:
|
selected:
|
||||||
"border border-[var(--color-border-default-primary,#141414)] text-[var(--color-content-inverse-primary,black)] bg-transparent hover:bg-[var(--color-surface-brand-accent,#4d4a00)]",
|
"border border-[var(--color-border-default-primary,#141414)] text-[var(--color-content-inverse-primary,black)] bg-transparent hover:bg-[var(--color-surface-inverse-brand-secondary)]",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get state styles based on mode
|
// Get state styles based on mode
|
||||||
|
|||||||
@@ -20,37 +20,31 @@ const LogoWallContainer = memo<LogoWallProps>(({ logos, className = "" }) => {
|
|||||||
src: getAssetPath(partnerLogoPath("food-not-bombs")),
|
src: getAssetPath(partnerLogoPath("food-not-bombs")),
|
||||||
alt: t("partners.foodNotBombs"),
|
alt: t("partners.foodNotBombs"),
|
||||||
size: "h-11 lg:h-14 xl:h-[70px]",
|
size: "h-11 lg:h-14 xl:h-[70px]",
|
||||||
order: "order-1 sm:order-4",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: getAssetPath(partnerLogoPath("start-coop")),
|
src: getAssetPath(partnerLogoPath("start-coop")),
|
||||||
alt: t("partners.startCoop"),
|
alt: t("partners.startCoop"),
|
||||||
size: "h-[42px] lg:h-[53px] xl:h-[66px]",
|
size: "h-[42px] lg:h-[53px] xl:h-[66px]",
|
||||||
order: "order-2 sm:order-2",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: getAssetPath(partnerLogoPath("metagov")),
|
src: getAssetPath(partnerLogoPath("metagov")),
|
||||||
alt: t("partners.metagov"),
|
alt: t("partners.metagov"),
|
||||||
size: "h-6 lg:h-8 xl:h-[41px]",
|
size: "h-6 lg:h-8 xl:h-[41px]",
|
||||||
order: "order-3 sm:order-1",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: getAssetPath(partnerLogoPath("open-civics")),
|
src: getAssetPath(partnerLogoPath("open-civics")),
|
||||||
alt: t("partners.openCivics"),
|
alt: t("partners.openCivics"),
|
||||||
size: "h-8 lg:h-10 xl:h-[50px]",
|
size: "h-8 lg:h-10 xl:h-[50px]",
|
||||||
order: "order-4 sm:order-5 md:order-6",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: getAssetPath(partnerLogoPath("mutual-aid-co")),
|
src: getAssetPath(partnerLogoPath("mutual-aid-co")),
|
||||||
alt: t("partners.mutualAidCo"),
|
alt: t("partners.mutualAidCo"),
|
||||||
size: "h-11 lg:h-14 xl:h-[70px]",
|
size: "h-11 lg:h-14 xl:h-[70px]",
|
||||||
order: "order-5 sm:order-6 md:order-5",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: getAssetPath(partnerLogoPath("cu-boulder")),
|
src: getAssetPath(partnerLogoPath("cu-boulder")),
|
||||||
alt: t("partners.cuBoulder"),
|
alt: t("partners.cuBoulder"),
|
||||||
size: "h-10 lg:h-12 xl:h-[60px]",
|
size: "h-10 lg:h-12 xl:h-[60px]",
|
||||||
order: "order-6 sm:order-3",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[t],
|
[t],
|
||||||
|
|||||||
@@ -14,18 +14,12 @@ function LogoWallView({
|
|||||||
className={`p-[var(--spacing-scale-032)] md:px-[var(--spacing-scale-024)] md:py-[var(--spacing-scale-032)] lg:px-[var(--spacing-scale-064)] lg:py-[var(--spacing-scale-048)] xl:px-[160px] xl:py-[var(--spacing-scale-064)] ${className}`}
|
className={`p-[var(--spacing-scale-032)] md:px-[var(--spacing-scale-024)] md:py-[var(--spacing-scale-032)] lg:px-[var(--spacing-scale-064)] lg:py-[var(--spacing-scale-048)] xl:px-[160px] xl:py-[var(--spacing-scale-064)] ${className}`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-[var(--spacing-scale-032)] md:gap-[var(--spacing-scale-024)] xl:gap-[var(--spacing-scale-032)]">
|
<div className="flex flex-col gap-[var(--spacing-scale-032)] md:gap-[var(--spacing-scale-024)] xl:gap-[var(--spacing-scale-032)]">
|
||||||
{/* Label */}
|
|
||||||
<p className="font-inter font-medium text-[10px] leading-[12px] xl:text-[14px] xl:leading-[12px] uppercase text-[var(--color-content-default-secondary)] text-center">
|
|
||||||
Trusted by leading cooperators
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Logo Grid Container */}
|
|
||||||
<div
|
<div
|
||||||
className={`transition-opacity duration-500 ${
|
className={`transition-opacity duration-500 ${
|
||||||
isVisible ? "opacity-60" : "opacity-0"
|
isVisible ? "opacity-60" : "opacity-0"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-2 grid-rows-3 sm:grid-cols-3 sm:grid-rows-2 md:flex md:justify-between md:items-center gap-x-[var(--spacing-scale-032)] gap-y-[var(--spacing-scale-032)] sm:gap-y-[var(--spacing-scale-048)]">
|
<div className="grid grid-cols-2 grid-rows-3 sm:grid-cols-3 sm:grid-rows-2 md:flex md:flex-wrap md:justify-center md:items-center gap-x-[var(--spacing-scale-032)] gap-y-[var(--spacing-scale-032)] sm:gap-y-[var(--spacing-scale-048)]">
|
||||||
{displayLogos.map((logo, index) => (
|
{displayLogos.map((logo, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "Your Article Title Here"
|
title: "Your Article Title Here"
|
||||||
description: "A brief, compelling description of what this article covers"
|
description: "A brief, compelling description of what this article covers"
|
||||||
author: "Author Name"
|
author: "CommunityRule"
|
||||||
date: "2025-01-15"
|
date: "2025-01-15"
|
||||||
related: ["slug-of-related-article-1", "slug-of-related-article-2"]
|
related: ["slug-of-related-article-1", "slug-of-related-article-2"]
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "Avoiding Burnout: Sustainability in the Ruins"
|
title: "Avoiding Burnout: Sustainability in the Ruins"
|
||||||
description: "Building a practice of resistance that doesn't consume you"
|
description: "Building a practice of resistance that doesn't consume you"
|
||||||
author: "Author name"
|
author: "CommunityRule"
|
||||||
date: "2025-08-12"
|
date: "2025-08-12"
|
||||||
related:
|
related:
|
||||||
- "resolving-active-conflicts"
|
- "resolving-active-conflicts"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "Digital Mediation and the Death of Nuance"
|
title: "Digital Mediation and the Death of Nuance"
|
||||||
description: "How corporate platforms undermine solidarity and what to build instead"
|
description: "How corporate platforms undermine solidarity and what to build instead"
|
||||||
author: "Author name"
|
author: "CommunityRule"
|
||||||
date: "2025-08-18"
|
date: "2025-08-18"
|
||||||
related:
|
related:
|
||||||
- "operational-security-mutual-aid"
|
- "operational-security-mutual-aid"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "How Chaos Concentrates Control"
|
title: "How Chaos Concentrates Control"
|
||||||
description: "How to limit informal hierarchies inevitably emerging in horizontal groups"
|
description: "How to limit informal hierarchies inevitably emerging in horizontal groups"
|
||||||
author: "Author name"
|
author: "CommunityRule"
|
||||||
date: "2025-08-15"
|
date: "2025-08-15"
|
||||||
related:
|
related:
|
||||||
- "making-decisions-without-hierarchy"
|
- "making-decisions-without-hierarchy"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "Integrating New Members Without Dilution"
|
title: "Integrating New Members Without Dilution"
|
||||||
description: "How to Bring New People In Without Everything Falling Apart"
|
description: "How to Bring New People In Without Everything Falling Apart"
|
||||||
author: "Author name"
|
author: "CommunityRule"
|
||||||
date: "2025-08-05"
|
date: "2025-08-05"
|
||||||
related:
|
related:
|
||||||
- "making-decisions-without-hierarchy"
|
- "making-decisions-without-hierarchy"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "Knowledge Management and Institutional Amnesia"
|
title: "Knowledge Management and Institutional Amnesia"
|
||||||
description: "Preserving what we learn without surveillance infrastructure"
|
description: "Preserving what we learn without surveillance infrastructure"
|
||||||
author: "Author name"
|
author: "CommunityRule"
|
||||||
date: "2025-08-20"
|
date: "2025-08-20"
|
||||||
related:
|
related:
|
||||||
- "integrating-new-members-without-dilution"
|
- "integrating-new-members-without-dilution"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "Making decisions without hierarchy"
|
title: "Making decisions without hierarchy"
|
||||||
description: "A brief guide to collaborative nonhierarchical decision making"
|
description: "A brief guide to collaborative nonhierarchical decision making"
|
||||||
author: "Author name"
|
author: "CommunityRule"
|
||||||
date: "2025-08-01"
|
date: "2025-08-01"
|
||||||
related:
|
related:
|
||||||
- "resolving-active-conflicts"
|
- "resolving-active-conflicts"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "Operational Security for Mutual Aid"
|
title: "Operational Security for Mutual Aid"
|
||||||
description: "Why protecting information isn't paranoia: it's care work in a hostile world"
|
description: "Why protecting information isn't paranoia: it's care work in a hostile world"
|
||||||
author: "Author name"
|
author: "CommunityRule"
|
||||||
date: "2025-08-10"
|
date: "2025-08-10"
|
||||||
related:
|
related:
|
||||||
- "resolving-active-conflicts"
|
- "resolving-active-conflicts"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "Resolving Active Conflicts"
|
title: "Resolving Active Conflicts"
|
||||||
description: "Practical steps for resolving conflicts while maintaining trust, cooperation, and shared goals"
|
description: "Practical steps for resolving conflicts while maintaining trust, cooperation, and shared goals"
|
||||||
author: "Author name"
|
author: "CommunityRule"
|
||||||
date: "2025-04-15"
|
date: "2025-04-15"
|
||||||
related:
|
related:
|
||||||
- "operational-security-mutual-aid"
|
- "operational-security-mutual-aid"
|
||||||
|
|||||||
@@ -209,7 +209,8 @@ export const ASSETS = {
|
|||||||
|
|
||||||
// Social media
|
// Social media
|
||||||
BLUESKY_LOGO: "assets/logos/bluesky.svg",
|
BLUESKY_LOGO: "assets/logos/bluesky.svg",
|
||||||
GITLAB_ICON: "assets/logos/gitlab.svg",
|
GITEA_ICON: "assets/logos/gitea.svg",
|
||||||
|
MASTODON_LOGO: "assets/logos/mastodon.svg",
|
||||||
|
|
||||||
// Content page decorative shapes
|
// Content page decorative shapes
|
||||||
CONTENT_SHAPE_1: "assets/shapes/content-shape-1.svg",
|
CONTENT_SHAPE_1: "assets/shapes/content-shape-1.svg",
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
},
|
},
|
||||||
"ariaLabels": {
|
"ariaLabels": {
|
||||||
"followBluesky": "Follow us on Bluesky",
|
"followBluesky": "Follow us on Bluesky",
|
||||||
"followGitlab": "Follow us on GitLab",
|
"viewSourceGitea": "View source on Gitea",
|
||||||
|
"followMastodon": "Follow us on Mastodon",
|
||||||
"featureToolsAndServices": "Feature tools and services",
|
"featureToolsAndServices": "Feature tools and services",
|
||||||
"askOrganizerContact": "Ask an organizer - Contact an organizer for help"
|
"askOrganizerContact": "Ask an organizer - Contact an organizer for help"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,19 @@
|
|||||||
},
|
},
|
||||||
"social": {
|
"social": {
|
||||||
"bluesky": {
|
"bluesky": {
|
||||||
"handle": "medlabboulder",
|
"label": "Bluesky",
|
||||||
"ariaLabel": "Follow us on Bluesky",
|
"ariaLabel": "Follow us on Bluesky",
|
||||||
"url": "https://bsky.app/profile/medlabboulder"
|
"url": "https://bsky.app/profile/medlabboulder.bsky.social"
|
||||||
},
|
},
|
||||||
"gitlab": {
|
"gitea": {
|
||||||
"handle": "medlabboulder",
|
"label": "Gitea",
|
||||||
"ariaLabel": "Follow us on GitLab",
|
"ariaLabel": "View source on Gitea",
|
||||||
"url": "https://gitlab.com/medlabboulder"
|
"url": "https://git.medlab.host/CommunityRule/community-rule"
|
||||||
|
},
|
||||||
|
"mastodon": {
|
||||||
|
"label": "Mastodon",
|
||||||
|
"ariaLabel": "Follow us on Mastodon",
|
||||||
|
"url": "https://social.medlab.host/@medlab"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"description": "We need your email to save your CommunityRule progress\nand make it accessible to you later.",
|
"description": "We need your email to save your CommunityRule progress\nand make it accessible to you later.",
|
||||||
"placeholder": "email@domain.com",
|
"placeholder": "email@domain.com",
|
||||||
"characterCountTemplate": "{current}/{max}",
|
"characterCountTemplate": "{current}/{max}",
|
||||||
"magicLinkSuccessTitle": "Check your email to log in!",
|
"magicLinkSuccessTitle": "Check your email to log in",
|
||||||
"magicLinkSuccessDescription": "Your account is created, now just check your email for a magic link",
|
"magicLinkSuccessDescription": "Your account has been created. A login link has been emailed to you.",
|
||||||
"magicLinkErrorTitle": "Could not send link"
|
"magicLinkErrorTitle": "Could not send link"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"addButtonText": "Add maturity"
|
"addButtonText": "Add maturity"
|
||||||
},
|
},
|
||||||
"organizationTypes": [
|
"organizationTypes": [
|
||||||
{ "label": "Worker’s coop" },
|
{ "label": "Worker cooperative" },
|
||||||
{ "label": "Mutual aid" },
|
{ "label": "Mutual aid" },
|
||||||
{ "label": "Open source project" },
|
{ "label": "Open source project" },
|
||||||
{ "label": "Nonprofit" },
|
{ "label": "Nonprofit" },
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
"title": "Who is this for?",
|
"title": "Who is this for?",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"title": "Worker's cooperatives",
|
"title": "Worker cooperatives",
|
||||||
"description": "Employee-owned businesses often need to clarify how power is shared, decisions are made, and how processes operate within their organizations."
|
"description": "Employee-owned businesses often need to clarify how power is shared, decisions are made, and how processes operate within their organizations."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<style>
|
||||||
|
.st1{fill:#fff}
|
||||||
|
</style>
|
||||||
|
<g id="Icon">
|
||||||
|
<circle cx="512" cy="512" r="512" style="fill:#609926"/>
|
||||||
|
<path class="st1" d="M762.2 350.3c-100.9 5.3-160.7 8-212 8.5v114.1l-16-7.9-.1-106.1c-58.9 0-110.7-3.1-209.1-8.6-12.3-.1-29.5-2.4-47.9-2.5-47.1-.1-110.2 33.5-106.7 118C175.8 597.6 296 609.9 344 610.9c5.3 24.7 61.8 110.1 103.6 114.6H631c109.9-8.2 192.3-373.8 131.2-375.2zm-546 117.3c-4.7-36.6 11.8-74.8 73.2-73.2C296.1 462 307 501.5 329 561.9c-56.2-7.4-104-25.7-112.8-94.3zm415.6 83.5-51.3 105.6c-6.5 13.4-22.7 19-36.2 12.5l-105.6-51.3c-13.4-6.5-19-22.7-12.5-36.2l51.3-105.6c6.5-13.4 22.7-19 36.2-12.5l105.6 51.3c13.4 6.6 19 22.8 12.5 36.2z"/>
|
||||||
|
<path class="st1" d="M555 609.9c.1-.2.2-.3.2-.5 17.2-35.2 24.3-49.8 19.8-62.4-3.9-11.1-15.5-16.6-36.7-26.6-.8-.4-1.7-.8-2.5-1.2.2-2.3-.1-4.7-1-7-.8-2.3-2.1-4.3-3.7-6l13.6-27.8-11.9-5.8-13.7 28.4c-2 0-4.1.3-6.2 1-8.9 3.2-13.5 13-10.3 21.9.7 1.9 1.7 3.5 2.8 5l-23.6 48.4c-1.9 0-3.8.3-5.7 1-8.9 3.2-13.5 13-10.3 21.9 3.2 8.9 13 13.5 21.9 10.3 8.9-3.2 13.5-13 10.3-21.9-.9-2.5-2.3-4.6-4-6.3l23-47.2c2.5.2 5 0 7.5-.9 2.1-.8 3.9-1.9 5.5-3.3.9.4 1.9.9 2.7 1.3 17.4 8.2 27.9 13.2 30 19.1 2.6 7.5-5.1 23.4-19.3 52.3-.1.2-.2.5-.4.7-2.2-.1-4.4.2-6.5 1-8.9 3.2-13.5 13-10.3 21.9 3.2 8.9 13 13.5 21.9 10.3 8.9-3.2 13.5-13 10.3-21.9-.6-2-1.9-4-3.4-5.7z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="22" height="22" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fill="#949494"
|
||||||
|
d="M44.9955 16.2709c0 -9.76202 -6.3993 -12.62323 -6.3993 -12.62323 -3.2287 -1.4803 -8.7692 -2.10373 -14.5254 -2.1506h-0.1415c-5.7562 0.04687 -11.293 0.6703 -14.51985 2.1506 0 0 -6.40027 2.86121 -6.40027 12.62323 0 2.2359 -0.04312 4.9078 0.02719 7.7427 0.2325 9.5455 1.75123 18.9551 10.58333 21.2913 4.0715 1.0772 7.5683 1.3022 10.3836 1.1475 5.1065 -0.2812 7.9687 -1.8206 7.9687 -1.8206l-0.1679 -3.7031s-3.6496 1.1503 -7.7474 1.0097c-4.0602 -0.1387 -8.3436 -0.4378 -8.9998 -5.4196 -0.0634 -0.4629 -0.0947 -0.9296 -0.0938 -1.3969 2.9714 0.6634 5.995 1.0664 9.0365 1.2047 3.089 0.1416 5.9849 -0.1809 8.9267 -0.5316 5.6418 -0.6731 10.5543 -4.1474 11.1711 -7.3208 0.9769 -5.0005 0.8981 -12.2033 0.8981 -12.2033ZM37.445 28.8483h-4.6874V17.3762c0 -2.4187 -1.0181 -3.6459 -3.0544 -3.6459 -2.2499 0 -3.3805 1.456 -3.3805 4.335v6.2812h-4.6556v-6.2812c0 -2.879 -1.125 -4.335 -3.3806 -4.335 -2.0362 0 -3.0543 1.2272 -3.0543 3.6459v11.4721h-4.6875V17.0284c0 -2.4156 0.6172 -4.334 1.8516 -5.7552 1.275 -1.4203 2.9437 -2.14873 5.0165 -2.14873 2.3981 0 4.214 0.92063 5.414 2.76373l1.169 1.9546 1.1672 -1.9546c1.2009 -1.8431 3.0159 -2.76373 5.414 -2.76373 2.0728 0 3.7415 0.72843 5.0165 2.14873 1.2368 1.42 1.8539 3.3384 1.8515 5.7552v11.8199Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 211 B After Width: | Height: | Size: 239 B |
|
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 375 B |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
@@ -3,6 +3,8 @@
|
|||||||
* Regenerate root favicon binaries from `public/assets/logos/community-rule.svg`.
|
* Regenerate root favicon binaries from `public/assets/logos/community-rule.svg`.
|
||||||
* Safari and iOS need PNG/ICO fallbacks; SVG alone shows a letter fallback in Safari.
|
* Safari and iOS need PNG/ICO fallbacks; SVG alone shows a letter fallback in Safari.
|
||||||
*
|
*
|
||||||
|
* Cream mark (#FFFDD2) on a transparent canvas — matches the brand SVG.
|
||||||
|
*
|
||||||
* Run: npm run generate:favicons
|
* Run: npm run generate:favicons
|
||||||
*/
|
*/
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
@@ -14,42 +16,24 @@ import pngToIco from "png-to-ico";
|
|||||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
const PUBLIC = path.join(ROOT, "public");
|
const PUBLIC = path.join(ROOT, "public");
|
||||||
const SVG_PATH = path.join(PUBLIC, "assets/logos/community-rule.svg");
|
const SVG_PATH = path.join(PUBLIC, "assets/logos/community-rule.svg");
|
||||||
const LOGO_FILL = "#FFFDD2";
|
|
||||||
const MARK_ON_LIGHT = "#000000";
|
|
||||||
|
|
||||||
async function readLogoSvg() {
|
async function readLogoSvg() {
|
||||||
return fs.readFile(SVG_PATH, "utf8");
|
return fs.readFile(SVG_PATH, "utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function markPng(svg, size, fill) {
|
/** Resize the logo SVG to a PNG with alpha (transparent background). */
|
||||||
const tinted = svg.replaceAll(LOGO_FILL, fill);
|
async function creamMarkTransparent(svg, size) {
|
||||||
return sharp(Buffer.from(tinted))
|
return sharp(Buffer.from(svg))
|
||||||
.resize(size, size, { fit: "contain" })
|
.resize(size, size, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
||||||
.png()
|
|
||||||
.toBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function creamMarkOnBlack(svg, size) {
|
|
||||||
const logoSize = Math.round(size * 0.75);
|
|
||||||
const logo = await markPng(svg, logoSize, LOGO_FILL);
|
|
||||||
return sharp({
|
|
||||||
create: {
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
channels: 4,
|
|
||||||
background: { r: 0, g: 0, b: 0, alpha: 1 },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.composite([{ input: logo, gravity: "center" }])
|
|
||||||
.png()
|
.png()
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const svg = await readLogoSvg();
|
const svg = await readLogoSvg();
|
||||||
const png16 = await markPng(svg, 16, MARK_ON_LIGHT);
|
const png16 = await creamMarkTransparent(svg, 16);
|
||||||
const png32 = await markPng(svg, 32, MARK_ON_LIGHT);
|
const png32 = await creamMarkTransparent(svg, 32);
|
||||||
const appleTouch = await creamMarkOnBlack(svg, 180);
|
const appleTouch = await creamMarkTransparent(svg, 180);
|
||||||
const faviconIco = await pngToIco([png16, png32]);
|
const faviconIco = await pngToIco([png16, png32]);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const WorkerCoopIcon = () => (
|
|||||||
export const Default = {
|
export const Default = {
|
||||||
args: {
|
args: {
|
||||||
icon: <WorkerCoopIcon />,
|
icon: <WorkerCoopIcon />,
|
||||||
title: "Worker's cooperatives",
|
title: "Worker cooperatives",
|
||||||
description:
|
description:
|
||||||
"Employee-owned businesses often need to clarify how power is shared, decisions are made, and how processes operate within their organizations.",
|
"Employee-owned businesses often need to clarify how power is shared, decisions are made, and how processes operate within their organizations.",
|
||||||
},
|
},
|
||||||
@@ -64,7 +64,7 @@ export const WithLongTitle = {
|
|||||||
export const WithShortDescription = {
|
export const WithShortDescription = {
|
||||||
args: {
|
args: {
|
||||||
icon: <WorkerCoopIcon />,
|
icon: <WorkerCoopIcon />,
|
||||||
title: "Worker's cooperatives",
|
title: "Worker cooperatives",
|
||||||
description: "Short description",
|
description: "Short description",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -222,10 +222,15 @@ export const Interactive = {
|
|||||||
});
|
});
|
||||||
await userEvent.click(blueskyLink);
|
await userEvent.click(blueskyLink);
|
||||||
|
|
||||||
const gitlabLink = canvas.getByRole("link", {
|
const giteaLink = canvas.getByRole("link", {
|
||||||
name: /follow us on gitlab/i,
|
name: /view source on gitea/i,
|
||||||
});
|
});
|
||||||
await userEvent.click(gitlabLink);
|
await userEvent.click(giteaLink);
|
||||||
|
|
||||||
|
const mastodonLink = canvas.getByRole("link", {
|
||||||
|
name: /follow us on mastodon/i,
|
||||||
|
});
|
||||||
|
await userEvent.click(mastodonLink);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -264,10 +269,15 @@ export const HoverStates = {
|
|||||||
await userEvent.hover(blueskyLink);
|
await userEvent.hover(blueskyLink);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
const gitlabLink = canvas.getByRole("link", {
|
const giteaLink = canvas.getByRole("link", {
|
||||||
name: /follow us on gitlab/i,
|
name: /view source on gitea/i,
|
||||||
});
|
});
|
||||||
await userEvent.hover(gitlabLink);
|
await userEvent.hover(giteaLink);
|
||||||
|
|
||||||
|
const mastodonLink = canvas.getByRole("link", {
|
||||||
|
name: /follow us on mastodon/i,
|
||||||
|
});
|
||||||
|
await userEvent.hover(mastodonLink);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const sampleItems = [
|
|||||||
width={36}
|
width={36}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
title: "Worker's cooperatives",
|
title: "Worker cooperatives",
|
||||||
description:
|
description:
|
||||||
"Employee-owned businesses often need to clarify how power is shared, decisions are made, and how processes operate within their organizations.",
|
"Employee-owned businesses often need to clarify how power is shared, decisions are made, and how processes operate within their organizations.",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export default {
|
|||||||
|
|
||||||
- **Mobile**: 3 rows × 2 columns grid with 32px gaps
|
- **Mobile**: 3 rows × 2 columns grid with 32px gaps
|
||||||
- **SM**: 2 rows × 3 columns grid with 48px row gap and 32px column gap
|
- **SM**: 2 rows × 3 columns grid with 48px row gap and 32px column gap
|
||||||
- **MD**: Single row with space-between layout and 24px gap between text and logos
|
- **MD+**: Centered flex-wrap row of logos
|
||||||
- **LG**: Larger logo sizes and 64px horizontal padding
|
- **LG**: Larger logo sizes and 64px horizontal padding
|
||||||
- **XL**: Largest logo sizes, 160px horizontal padding, and 14px label text
|
- **XL**: Largest logo sizes and 160px horizontal padding
|
||||||
|
|
||||||
## Animations & Transitions
|
## Animations & Transitions
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export default {
|
|||||||
|
|
||||||
## Props
|
## Props
|
||||||
|
|
||||||
- **logos** (optional): Array of logo objects with src, alt, size, and order properties. If not provided, uses default partner logos.
|
- **logos** (optional): Array of logo objects with src, alt, and size properties. If not provided, uses default partner logos.
|
||||||
|
|
||||||
## Usage Examples
|
## Usage Examples
|
||||||
|
|
||||||
@@ -40,13 +40,11 @@ export default {
|
|||||||
src: "assets/logos/partners/cu-boulder.svg",
|
src: "assets/logos/partners/cu-boulder.svg",
|
||||||
alt: "CU Boulder",
|
alt: "CU Boulder",
|
||||||
size: "h-10 lg:h-12 xl:h-[60px]",
|
size: "h-10 lg:h-12 xl:h-[60px]",
|
||||||
order: "order-1 sm:order-2"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: "assets/logos/partners/food-not-bombs.svg",
|
src: "assets/logos/partners/food-not-bombs.svg",
|
||||||
alt: "Food Not Bombs",
|
alt: "Food Not Bombs",
|
||||||
size: "h-11 lg:h-14 xl:h-[70px]",
|
size: "h-11 lg:h-14 xl:h-[70px]",
|
||||||
order: "order-2 sm:order-1"
|
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -65,7 +63,7 @@ This will fall back to the default partner logos.`,
|
|||||||
logos: {
|
logos: {
|
||||||
control: "object",
|
control: "object",
|
||||||
description:
|
description:
|
||||||
"Array of logo objects with src, alt, size, and order properties. If not provided, uses default partner logos.",
|
"Array of logo objects with src, alt, and size properties. If not provided, uses default partner logos.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,7 +54,10 @@ describe("Footer (behavioral tests)", () => {
|
|||||||
screen.getAllByRole("link", { name: "Follow us on Bluesky" }).length,
|
screen.getAllByRole("link", { name: "Follow us on Bluesky" }).length,
|
||||||
).toBeGreaterThan(0);
|
).toBeGreaterThan(0);
|
||||||
expect(
|
expect(
|
||||||
screen.getAllByRole("link", { name: "Follow us on GitLab" }).length,
|
screen.getAllByRole("link", { name: "View source on Gitea" }).length,
|
||||||
|
).toBeGreaterThan(0);
|
||||||
|
expect(
|
||||||
|
screen.getAllByRole("link", { name: "Follow us on Mastodon" }).length,
|
||||||
).toBeGreaterThan(0);
|
).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,7 +77,7 @@ describe("Footer (behavioral tests)", () => {
|
|||||||
it("renders navigation links with baseline width-fit focus targets", () => {
|
it("renders navigation links with baseline width-fit focus targets", () => {
|
||||||
render(<Footer />);
|
render(<Footer />);
|
||||||
const useCases = screen.getAllByRole("link", { name: "Use cases" })[0];
|
const useCases = screen.getAllByRole("link", { name: "Use cases" })[0];
|
||||||
expect(useCases).toHaveClass("w-fit", "self-start");
|
expect(useCases).toHaveClass("w-fit", "self-start", "md:self-end");
|
||||||
expect(useCases).not.toHaveClass("w-full");
|
expect(useCases).not.toHaveClass("w-full");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type IconProps = React.ComponentProps<typeof Icon>;
|
|||||||
|
|
||||||
const baseProps: IconProps = {
|
const baseProps: IconProps = {
|
||||||
icon: <div data-testid="test-icon">Icon</div>,
|
icon: <div data-testid="test-icon">Icon</div>,
|
||||||
title: "Worker's cooperatives",
|
title: "Worker cooperatives",
|
||||||
description:
|
description:
|
||||||
"Employee-owned businesses often need to clarify how power is shared",
|
"Employee-owned businesses often need to clarify how power is shared",
|
||||||
};
|
};
|
||||||
@@ -89,12 +89,12 @@ describe("Icon (behavioral tests)", () => {
|
|||||||
render(
|
render(
|
||||||
<Icon
|
<Icon
|
||||||
icon={<div data-testid="icon">Icon</div>}
|
icon={<div data-testid="icon">Icon</div>}
|
||||||
title="Worker's cooperatives"
|
title="Worker cooperatives"
|
||||||
description="Employee-owned businesses"
|
description="Employee-owned businesses"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId("icon")).toBeInTheDocument();
|
expect(screen.getByTestId("icon")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Worker's cooperatives")).toBeInTheDocument();
|
expect(screen.getByText("Worker cooperatives")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Employee-owned businesses")).toBeInTheDocument();
|
expect(screen.getByText("Employee-owned businesses")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -166,6 +166,30 @@ describe("MultiSelect – behaviour specifics", () => {
|
|||||||
expect(handleConfirm).toHaveBeenCalledWith("custom-1", "NewOption");
|
expect(handleConfirm).toHaveBeenCalledWith("custom-1", "NewOption");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("allows spaces in custom chip labels", async () => {
|
||||||
|
const handleConfirm = vi.fn();
|
||||||
|
const customOptions = [
|
||||||
|
{ id: "custom-1", label: "", state: "custom" as const },
|
||||||
|
];
|
||||||
|
render(
|
||||||
|
<MultiSelect
|
||||||
|
options={customOptions}
|
||||||
|
onCustomChipConfirm={handleConfirm}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByPlaceholderText("Type to add");
|
||||||
|
await userEvent.type(input, "Mutual aid network");
|
||||||
|
|
||||||
|
const checkButton = screen.getByRole("button", { name: "Confirm" });
|
||||||
|
await userEvent.click(checkButton);
|
||||||
|
|
||||||
|
expect(handleConfirm).toHaveBeenCalledWith(
|
||||||
|
"custom-1",
|
||||||
|
"Mutual aid network",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("handles custom chip close", async () => {
|
it("handles custom chip close", async () => {
|
||||||
const handleClose = vi.fn();
|
const handleClose = vi.fn();
|
||||||
const customOptions = [
|
const customOptions = [
|
||||||
|
|||||||
@@ -101,9 +101,7 @@ test.describe("Critical User Journeys", () => {
|
|||||||
|
|
||||||
// Check key components are rendered
|
// Check key components are rendered
|
||||||
await expect(page.locator('img[alt="Hero illustration"]')).toBeVisible();
|
await expect(page.locator('img[alt="Hero illustration"]')).toBeVisible();
|
||||||
await expect(
|
await expect(page.locator('img[alt="Food Not Bombs"]')).toBeVisible();
|
||||||
page.locator("text=Trusted by leading cooperators"),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(page.locator("text=Jo Freeman")).toBeVisible();
|
await expect(page.locator("text=Jo Freeman")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const mockPosts = [
|
|||||||
frontmatter: {
|
frontmatter: {
|
||||||
title: "Resolving Active Conflicts",
|
title: "Resolving Active Conflicts",
|
||||||
description: "Practical steps for resolving conflicts",
|
description: "Practical steps for resolving conflicts",
|
||||||
author: "Author name",
|
author: "CommunityRule",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
thumbnail: {
|
thumbnail: {
|
||||||
vertical: "resolving-active-conflicts-vertical.svg",
|
vertical: "resolving-active-conflicts-vertical.svg",
|
||||||
@@ -48,7 +48,7 @@ const mockPosts = [
|
|||||||
frontmatter: {
|
frontmatter: {
|
||||||
title: "Operational Security for Mutual Aid",
|
title: "Operational Security for Mutual Aid",
|
||||||
description: "Tactics to protect members",
|
description: "Tactics to protect members",
|
||||||
author: "Author name",
|
author: "CommunityRule",
|
||||||
date: "2025-04-10",
|
date: "2025-04-10",
|
||||||
thumbnail: {
|
thumbnail: {
|
||||||
vertical: "operational-security-mutual-aid-vertical.svg",
|
vertical: "operational-security-mutual-aid-vertical.svg",
|
||||||
|
|||||||
@@ -134,12 +134,16 @@ describe("User Journey Integration", () => {
|
|||||||
const blueskyLink = screen.getByRole("link", {
|
const blueskyLink = screen.getByRole("link", {
|
||||||
name: "Follow us on Bluesky",
|
name: "Follow us on Bluesky",
|
||||||
});
|
});
|
||||||
const gitlabLink = screen.getByRole("link", {
|
const giteaLink = screen.getByRole("link", {
|
||||||
name: "Follow us on GitLab",
|
name: "View source on Gitea",
|
||||||
|
});
|
||||||
|
const mastodonLink = screen.getByRole("link", {
|
||||||
|
name: "Follow us on Mastodon",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(blueskyLink).toBeInTheDocument();
|
expect(blueskyLink).toBeInTheDocument();
|
||||||
expect(gitlabLink).toBeInTheDocument();
|
expect(giteaLink).toBeInTheDocument();
|
||||||
|
expect(mastodonLink).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("user explores features and benefits", async () => {
|
test("user explores features and benefits", async () => {
|
||||||
@@ -179,9 +183,11 @@ describe("User Journey Integration", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const blueskyLink = screen.getByRole("link", { name: /Bluesky/i });
|
const blueskyLink = screen.getByRole("link", { name: /Bluesky/i });
|
||||||
const gitlabLink = screen.getByRole("link", { name: /GitLab/i });
|
const giteaLink = screen.getByRole("link", { name: /Gitea/i });
|
||||||
|
const mastodonLink = screen.getByRole("link", { name: /Mastodon/i });
|
||||||
expect(blueskyLink).toBeInTheDocument();
|
expect(blueskyLink).toBeInTheDocument();
|
||||||
expect(gitlabLink).toBeInTheDocument();
|
expect(giteaLink).toBeInTheDocument();
|
||||||
|
expect(mastodonLink).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("user completes the full journey from discovery to action", async () => {
|
test("user completes the full journey from discovery to action", async () => {
|
||||||
|
|||||||
@@ -42,14 +42,6 @@ describe("LogoWall Component", () => {
|
|||||||
expect(screen.queryByAltText("Food Not Bombs")).not.toBeInTheDocument();
|
expect(screen.queryByAltText("Food Not Bombs")).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("renders section label", () => {
|
|
||||||
render(<LogoWall />);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
screen.getByText("Trusted by leading cooperators"),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("applies correct CSS classes", () => {
|
test("applies correct CSS classes", () => {
|
||||||
render(<LogoWall />);
|
render(<LogoWall />);
|
||||||
|
|
||||||
@@ -74,7 +66,12 @@ describe("LogoWall Component", () => {
|
|||||||
'[class*="grid grid-cols-2 grid-rows-3"]',
|
'[class*="grid grid-cols-2 grid-rows-3"]',
|
||||||
);
|
);
|
||||||
expect(grid).toBeInTheDocument();
|
expect(grid).toBeInTheDocument();
|
||||||
expect(grid).toHaveClass("sm:grid-cols-3", "sm:grid-rows-2", "md:flex");
|
expect(grid).toHaveClass(
|
||||||
|
"sm:grid-cols-3",
|
||||||
|
"sm:grid-rows-2",
|
||||||
|
"md:flex",
|
||||||
|
"md:justify-center",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("renders logos with correct attributes", () => {
|
test("renders logos with correct attributes", () => {
|
||||||
@@ -88,15 +85,6 @@ describe("LogoWall Component", () => {
|
|||||||
expect(foodNotBombsLogo).toHaveClass("h-11", "lg:h-14", "xl:h-[70px]");
|
expect(foodNotBombsLogo).toHaveClass("h-11", "lg:h-14", "xl:h-[70px]");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("applies order classes for responsive layout", () => {
|
|
||||||
render(<LogoWall />);
|
|
||||||
|
|
||||||
const foodNotBombsContainer = screen
|
|
||||||
.getByAltText("Food Not Bombs")
|
|
||||||
.closest("div");
|
|
||||||
expect(foodNotBombsContainer).toHaveClass("order-1", "sm:order-4");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("handles empty logos array", () => {
|
test("handles empty logos array", () => {
|
||||||
render(<LogoWall logos={[]} />);
|
render(<LogoWall logos={[]} />);
|
||||||
|
|
||||||
@@ -119,9 +107,7 @@ describe("LogoWall Component", () => {
|
|||||||
const section = document.querySelector("section");
|
const section = document.querySelector("section");
|
||||||
expect(section).toBeInTheDocument();
|
expect(section).toBeInTheDocument();
|
||||||
|
|
||||||
// Check for the label
|
expect(screen.queryByText("Trusted by leading cooperators")).not.toBeInTheDocument();
|
||||||
const label = screen.getByText("Trusted by leading cooperators");
|
|
||||||
expect(label).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("applies transition effects", () => {
|
test("applies transition effects", () => {
|
||||||
|
|||||||