Match the 3 Nov color ramps and size-based strokes, and replace leftover palette hex with tokens.

Drop Cookies Settings from the footer, link the Home features that already have pages, and give leftover routes their own descriptions.
This commit is contained in:
adilallo committed 2026-11-05 15:37:55 -07:00
1 parent 8d07e00667
commit ca928e306b
32 files changed
+276 -213

No files matched your search

+1
View File
@@ -5,6 +5,7 @@ import MonitorPageContent from "./MonitorPageContent";
export const metadata: Metadata = routeMetadata("/monitor", { export const metadata: Metadata = routeMetadata("/monitor", {
title: messages.metadata.monitor.title, title: messages.metadata.monitor.title,
description: messages.metadata.monitor.description,
}); });
export default function MonitorPage() { export default function MonitorPage() {
+1
View File
@@ -4,6 +4,7 @@ import { NO_INDEX_ROBOTS, routeMetadata } from "../../../lib/siteMetadata";
export const metadata: Metadata = routeMetadata("/login", { export const metadata: Metadata = routeMetadata("/login", {
title: messages.metadata.login.title, title: messages.metadata.login.title,
description: messages.metadata.login.description,
robots: NO_INDEX_ROBOTS, robots: NO_INDEX_ROBOTS,
}); });
+1
View File
@@ -5,6 +5,7 @@ import ProfilePageClient from "./ProfilePageClient";
export const metadata: Metadata = routeMetadata("/profile", { export const metadata: Metadata = routeMetadata("/profile", {
title: messages.metadata.profile.title, title: messages.metadata.profile.title,
description: messages.metadata.profile.description,
robots: NO_INDEX_ROBOTS, robots: NO_INDEX_ROBOTS,
}); });
+17 -9
View File
@@ -112,6 +112,14 @@ const Button = memo<ButtonProps>(
xlarge: "p-[var(--space-400)] gap-[var(--space-200)]", xlarge: "p-[var(--space-400)] gap-[var(--space-200)]",
}; };
const strokeStyles: Record<string, string> = {
xsmall: "border-[1px] active:border-[1.25px]",
small: "border-[1.25px] active:border-[1.5px]",
medium: "border-[1.5px] active:border-[1.75px]",
large: "border-[1.75px]",
xlarge: "border-[2px]",
};
const fontStyles: Record<string, string> = { const fontStyles: Record<string, string> = {
xsmall: "text-xx-small-label", xsmall: "text-xx-small-label",
small: "text-x-small-label", small: "text-x-small-label",
@@ -122,21 +130,21 @@ const Button = memo<ButtonProps>(
const variantStyles: Record<string, string> = { const variantStyles: Record<string, string> = {
filled: filled:
"bg-[var(--color-surface-invert-primary)] text-[var(--color-content-invert-primary)] border-[1.5px] border-transparent hover:bg-[var(--color-surface-invert-primary)] hover:text-[var(--color-content-invert-brand-primary)] hover:border-[var(--color-border-invert-brand-primary)] focus-visible:bg-[var(--color-surface-invert-primary)] focus-visible:text-[var(--color-content-invert-brand-primary)] focus-visible:outline-none focus-visible:border-transparent focus-visible:shadow-[var(--shadow-focus-default)] active:bg-[var(--color-surface-invert-brand-primary)] active:text-[var(--color-content-invert-primary)] active:border-[var(--color-border-invert-brand-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-default-secondary)] disabled:text-[var(--color-content-invert-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100", "bg-[var(--color-surface-invert-primary)] text-[var(--color-content-invert-primary)] border-transparent hover:bg-[var(--color-surface-invert-primary)] hover:text-[var(--color-content-invert-brand-primary)] hover:border-[var(--color-border-invert-brand-primary)] focus-visible:bg-[var(--color-surface-invert-primary)] focus-visible:text-[var(--color-content-invert-brand-primary)] focus-visible:outline-none focus-visible:border-transparent focus-visible:shadow-[var(--shadow-focus-default)] active:bg-[var(--color-surface-invert-brand-primary)] active:text-[var(--color-content-invert-primary)] active:border-[var(--color-border-invert-brand-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-default-secondary)] disabled:text-[var(--color-content-invert-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100",
"filled-inverse": "filled-inverse":
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border-[1.5px] border-transparent hover:bg-[var(--color-surface-default-primary)] hover:text-[var(--color-content-default-brand-primary)] hover:border-[var(--color-border-default-brand-primary)] focus-visible:bg-[var(--color-surface-default-primary)] focus-visible:text-[var(--color-content-default-brand-primary)] focus-visible:outline-none focus-visible:border-transparent focus-visible:shadow-[var(--shadow-focus-invert)] active:bg-[var(--color-surface-default-brand-primary)] active:text-[var(--color-content-default-primary)] active:border-[var(--color-border-default-brand-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-invert-secondary)] disabled:text-[var(--color-content-default-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100", "bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border-transparent hover:bg-[var(--color-surface-default-primary)] hover:text-[var(--color-content-default-brand-primary)] hover:border-[var(--color-border-default-brand-primary)] focus-visible:bg-[var(--color-surface-default-primary)] focus-visible:text-[var(--color-content-default-brand-primary)] focus-visible:outline-none focus-visible:border-transparent focus-visible:shadow-[var(--shadow-focus-invert)] active:bg-[var(--color-surface-default-brand-primary)] active:text-[var(--color-content-default-primary)] active:border-[var(--color-border-default-brand-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-invert-secondary)] disabled:text-[var(--color-content-default-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100",
outline: outline:
"bg-transparent text-[var(--color-content-default-primary)] border-[1.5px] border-[var(--color-border-invert-primary)] hover:bg-transparent hover:text-[var(--color-content-default-brand-primary)] hover:border-[1.5px] hover:border-[var(--color-border-default-brand-primary)] focus-visible:bg-transparent focus-visible:text-[var(--color-content-default-primary)] focus-visible:outline-none focus-visible:border-[1.5px] focus-visible:border-[var(--color-border-invert-primary)] focus-visible:shadow-[var(--shadow-focus-default)] active:bg-transparent active:text-[var(--color-content-default-primary)] active:border-[1.5px] active:border-[var(--color-border-default-brand-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-default-secondary)] disabled:text-[var(--color-content-invert-tertiary)] disabled:border-[1.5px] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100", "bg-transparent text-[var(--color-content-default-primary)] border-[var(--color-border-invert-primary)] hover:bg-transparent hover:text-[var(--color-content-default-brand-primary)] hover:border-[var(--color-border-default-brand-primary)] focus-visible:bg-transparent focus-visible:text-[var(--color-content-default-primary)] focus-visible:outline-none focus-visible:border-[var(--color-border-invert-primary)] focus-visible:shadow-[var(--shadow-focus-default)] active:bg-transparent active:text-[var(--color-content-default-primary)] active:border-[var(--color-border-default-brand-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-default-secondary)] disabled:text-[var(--color-content-invert-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100",
"outline-inverse": "outline-inverse":
"bg-transparent text-[var(--color-content-invert-primary)] border-[1.5px] border-[var(--color-border-default-primary)] hover:bg-transparent hover:text-[var(--color-content-invert-brand-primary)] hover:border-[1.5px] hover:border-[var(--color-border-invert-brand-primary)] focus-visible:bg-transparent focus-visible:text-[var(--color-content-invert-primary)] focus-visible:outline-none focus-visible:border-[1.5px] focus-visible:border-[var(--color-border-default-primary)] focus-visible:shadow-[var(--shadow-focus-invert)] active:bg-transparent active:text-[var(--color-content-invert-primary)] active:border-[1.5px] active:border-[var(--color-border-invert-brand-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-invert-secondary)] disabled:text-[var(--color-content-default-tertiary)] disabled:border-[1.5px] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100", "bg-transparent text-[var(--color-content-invert-primary)] border-[var(--color-border-default-primary)] hover:bg-transparent hover:text-[var(--color-content-invert-brand-primary)] hover:border-[var(--color-border-invert-brand-primary)] focus-visible:bg-transparent focus-visible:text-[var(--color-content-invert-primary)] focus-visible:outline-none focus-visible:border-[var(--color-border-default-primary)] focus-visible:shadow-[var(--shadow-focus-invert)] active:bg-transparent active:text-[var(--color-content-invert-primary)] active:border-[var(--color-border-invert-brand-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-invert-secondary)] disabled:text-[var(--color-content-default-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100",
ghost: ghost:
"bg-transparent text-[var(--color-content-default-brand-primary)] border-[1.5px] border-transparent hover:bg-transparent hover:text-[var(--color-content-default-primary)] hover:border-transparent focus-visible:bg-transparent focus-visible:text-[var(--color-content-default-brand-primary)] focus-visible:outline-none focus-visible:border-transparent focus-visible:shadow-[var(--shadow-focus-default)] active:bg-transparent active:text-[var(--color-content-default-primary)] active:border-[var(--color-border-default-brand-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-default-secondary)] disabled:text-[var(--color-content-invert-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100", "bg-transparent text-[var(--color-content-default-brand-primary)] border-transparent hover:bg-transparent hover:text-[var(--color-content-default-primary)] hover:border-transparent focus-visible:bg-transparent focus-visible:text-[var(--color-content-default-brand-primary)] focus-visible:outline-none focus-visible:border-transparent focus-visible:shadow-[var(--shadow-focus-default)] active:bg-transparent active:text-[var(--color-content-default-primary)] active:border-[var(--color-border-default-brand-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-default-secondary)] disabled:text-[var(--color-content-invert-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100",
"ghost-inverse": "ghost-inverse":
"bg-transparent text-[var(--color-content-invert-primary)] border-[1.5px] border-transparent hover:bg-transparent hover:text-[var(--color-content-invert-primary)] hover:border-transparent focus-visible:bg-transparent focus-visible:text-[var(--color-content-invert-brand-primary)] focus-visible:outline-none focus-visible:border-transparent focus-visible:shadow-[var(--shadow-focus-invert)] active:bg-[var(--color-surface-invert-brand-primary)] active:text-[var(--color-content-invert-primary)] active:border-[var(--color-border-invert-brand-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-invert-secondary)] disabled:text-[var(--color-content-default-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100", "bg-transparent text-[var(--color-content-invert-primary)] border-transparent hover:bg-transparent hover:text-[var(--color-content-invert-primary)] hover:border-transparent focus-visible:bg-transparent focus-visible:text-[var(--color-content-invert-brand-primary)] focus-visible:outline-none focus-visible:border-transparent focus-visible:shadow-[var(--shadow-focus-invert)] active:bg-[var(--color-surface-invert-brand-primary)] active:text-[var(--color-content-invert-primary)] active:border-[var(--color-border-invert-brand-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-invert-secondary)] disabled:text-[var(--color-content-default-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100",
danger: danger:
"bg-transparent text-[var(--color-content-default-negative-primary)] border border-[var(--color-border-default-negative-primary)] hover:bg-[var(--color-surface-invert-negative-secondary)] hover:text-[var(--color-content-invert-negative-primary)] hover:border-[var(--color-border-default-negative-primary)] focus-visible:bg-transparent focus-visible:text-[var(--color-content-default-negative-primary)] focus-visible:outline-none focus-visible:border-[var(--color-border-default-negative-primary)] focus-visible:shadow-[var(--shadow-focus-default)] active:bg-[var(--color-surface-invert-negative-primary)] active:text-[var(--color-content-invert-primary)] active:border-[1.5px] active:border-[var(--color-border-default-negative-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-default-secondary)] disabled:text-[var(--color-content-invert-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100", "bg-transparent text-[var(--color-content-default-negative-primary)] border-[var(--color-border-default-negative-primary)] hover:bg-[var(--color-surface-invert-negative-secondary)] hover:text-[var(--color-content-invert-negative-primary)] hover:border-[var(--color-border-default-negative-primary)] focus-visible:bg-transparent focus-visible:text-[var(--color-content-default-negative-primary)] focus-visible:outline-none focus-visible:border-[var(--color-border-default-negative-primary)] focus-visible:shadow-[var(--shadow-focus-default)] active:bg-[var(--color-surface-invert-negative-primary)] active:text-[var(--color-content-invert-primary)] active:border-[var(--color-border-default-negative-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-default-secondary)] disabled:text-[var(--color-content-invert-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100",
"danger-inverse": "danger-inverse":
"bg-transparent text-[var(--color-content-invert-negative-primary)] border border-[var(--color-border-invert-negative-primary)] hover:bg-transparent hover:text-[var(--color-content-invert-negative-primary)] hover:border-[var(--color-border-invert-negative-primary)] focus-visible:bg-transparent focus-visible:text-[var(--color-content-invert-negative-primary)] focus-visible:outline-none focus-visible:border-[var(--color-border-invert-negative-primary)] focus-visible:shadow-[var(--shadow-focus-invert)] active:bg-[var(--color-surface-default-negative-primary)] active:text-[var(--color-content-default-primary)] active:border-[1.5px] active:border-[var(--color-border-default-negative-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-invert-secondary)] disabled:text-[var(--color-content-default-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100", "bg-transparent text-[var(--color-content-invert-negative-primary)] border-[var(--color-border-invert-negative-primary)] hover:bg-transparent hover:text-[var(--color-content-invert-negative-primary)] hover:border-[var(--color-border-invert-negative-primary)] focus-visible:bg-transparent focus-visible:text-[var(--color-content-invert-negative-primary)] focus-visible:outline-none focus-visible:border-[var(--color-border-invert-negative-primary)] focus-visible:shadow-[var(--shadow-focus-invert)] active:bg-[var(--color-surface-default-negative-primary)] active:text-[var(--color-content-default-primary)] active:border-[var(--color-border-default-negative-primary)] active:shadow-none active:scale-[0.98] disabled:bg-[var(--color-surface-invert-secondary)] disabled:text-[var(--color-content-default-tertiary)] disabled:border-transparent disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100",
}; };
// Hover/focus: color + border only — no scale and no extra outline (those // Hover/focus: color + border only — no scale and no extra outline (those
@@ -144,7 +152,7 @@ const Button = memo<ButtonProps>(
// and cards: color, opacity, or shadow on hover; scale only on press. // and cards: color, opacity, or shadow on hover; scale only on press.
// State prop is informational for Figma alignment; CSS pseudo-classes own // State prop is informational for Figma alignment; CSS pseudo-classes own
// the actual interaction states. // the actual interaction states.
const baseStyles = `inline-flex items-center justify-start box-border whitespace-nowrap shrink-0 touch-manipulation [-webkit-tap-highlight-color:transparent] ${sizeStyles[size]} rounded-[var(--radius-full)] ${fontStyles[size]} transition-[transform,color,background-color,border-color,box-shadow] duration-150 ease-out cursor-pointer ${variantStyles[variant]}`; const baseStyles = `inline-flex items-center justify-start box-border whitespace-nowrap shrink-0 touch-manipulation [-webkit-tap-highlight-color:transparent] ${sizeStyles[size]} rounded-[var(--radius-full)] ${fontStyles[size]} transition-[transform,color,background-color,border-color,box-shadow] duration-150 ease-out cursor-pointer ${variantStyles[variant]} ${strokeStyles[size]}`;
const combinedStyles = `${baseStyles} ${className}`; const combinedStyles = `${baseStyles} ${className}`;
const sharedA11y = { const sharedA11y = {
+4 -4
View File
@@ -21,7 +21,7 @@ function StatView({
return ( return (
<article <article
className={`relative flex h-auto min-h-[182px] w-full flex-col items-start justify-between rounded-[var(--radius-500)] bg-[var(--color-surface-invert-primary,white)] px-[var(--space-600)] py-[var(--space-800)] sm:h-[170px] sm:min-h-0 sm:p-[var(--space-600)] ${className}`.trim()} className={`relative flex h-auto min-h-[182px] w-full flex-col items-start justify-between rounded-[var(--radius-500)] bg-[var(--color-surface-invert-primary)] px-[var(--space-600)] py-[var(--space-800)] sm:h-[170px] sm:min-h-0 sm:p-[var(--space-600)] ${className}`.trim()}
> >
<div className="relative flex w-full flex-col items-start"> <div className="relative flex w-full flex-col items-start">
<div className="relative flex items-center"> <div className="relative flex items-center">
@@ -29,16 +29,16 @@ function StatView({
variant={shapeVariant} variant={shapeVariant}
className="absolute -left-[11px] -top-[21px] size-[80px] rotate-[15deg] opacity-90" className="absolute -left-[11px] -top-[21px] size-[80px] rotate-[15deg] opacity-90"
/> />
<p className="relative font-bricolage-grotesque text-xx-large-heading text-[var(--color-content-invert-primary,black)]"> <p className="relative font-bricolage-grotesque text-xx-large-heading text-[var(--color-content-invert-primary)]">
{value} {value}
</p> </p>
</div> </div>
<p className="text-small-paragraph text-[var(--color-content-invert-primary,black)]"> <p className="text-small-paragraph text-[var(--color-content-invert-primary)]">
{label} {label}
</p> </p>
</div> </div>
{asOf ? ( {asOf ? (
<p className="w-full text-xx-small-paragraph text-[var(--color-content-invert-tertiary,#2d2d2d)]"> <p className="w-full text-xx-small-paragraph text-[var(--color-content-invert-tertiary)]">
{sourcedYear ? ( {sourcedYear ? (
<> <>
{asOfPrefix ? <span>{`${asOfPrefix} `}</span> : null} {asOfPrefix ? <span>{`${asOfPrefix} `}</span> : null}
@@ -33,10 +33,10 @@ const ContentContainerContainer = memo<ContentContainerProps>(
const onLight = tone === "onLight"; const onLight = tone === "onLight";
const titleColor = onLight const titleColor = onLight
? "text-[var(--color-content-default-primary)] group-hover:text-[var(--color-content-default-brand-primary)]" ? "text-[var(--color-content-default-primary)] group-hover:text-[var(--color-content-default-brand-primary)]"
: "text-[var(--color-content-invert-brand-royal)] group-hover:text-blue-200"; : "text-[var(--color-content-invert-brand-navy)] group-hover:text-[var(--color-content-invert-brand-royal)]";
const bodyColor = onLight const bodyColor = onLight
? "text-[var(--color-content-default-secondary)]" ? "text-[var(--color-content-default-secondary)]"
: "text-[var(--color-content-invert-brand-royal)]"; : "text-[var(--color-content-invert-brand-navy)]";
const getIconImage = (slug: string): string => { const getIconImage = (slug: string): string => {
const resolvedSlug = const resolvedSlug =
@@ -56,7 +56,7 @@ const CheckboxContainer = memo<CheckboxProps>(
// Default state: tertiary border, with hover and focus states via CSS // Default state: tertiary border, with hover and focus states via CSS
// Hover changes border to brand primary color // Hover changes border to brand primary color
// Focus removes border and shows shadow (double ring: 2px white inner, 4px dark outer) // Focus removes border and shows shadow (double ring: 2px white inner, 4px dark outer)
return `${baseBox} bg-[var(--color-surface-default-primary)] border border-solid border-[var(--color-border-default-control)] hover:border-[var(--color-border-default-brand-primary,#fdfaa8)] focus:border-transparent focus:shadow-[var(--shadow-focus-invert)] focus:outline-none`; return `${baseBox} bg-[var(--color-surface-default-primary)] border border-solid border-[var(--color-border-default-control)] hover:border-[var(--color-border-default-brand-primary)] focus:border-transparent focus:shadow-[var(--shadow-focus-invert)] focus:outline-none`;
} }
// Inverse mode styles per Figma // Inverse mode styles per Figma
@@ -64,7 +64,7 @@ const CheckboxContainer = memo<CheckboxProps>(
// Inverse: transparent background, white border // Inverse: transparent background, white border
// Hover changes border to brand primary color // Hover changes border to brand primary color
// Focus shows shadow (2px dark inner, 4px white outer) - note: reversed from standard // Focus shows shadow (2px dark inner, 4px white outer) - note: reversed from standard
return `${baseBox} bg-transparent border border-solid border-[var(--color-border-invert-primary,white)] hover:border-[var(--color-border-default-brand-primary,#fdfaa8)] focus:shadow-[var(--shadow-focus-default)] focus:outline-none`; return `${baseBox} bg-transparent border border-solid border-[var(--color-border-invert-primary)] hover:border-[var(--color-border-default-brand-primary)] focus:shadow-[var(--shadow-focus-default)] focus:outline-none`;
} }
return baseBox; return baseBox;
@@ -75,8 +75,8 @@ const CheckboxContainer = memo<CheckboxProps>(
// Checkmark color per Figma // Checkmark color per Figma
const checkGlyphColor = checked const checkGlyphColor = checked
? isStandard ? isStandard
? "var(--color-content-default-brand-primary, #fefcc9)" // Light yellow/cream for standard mode ? "var(--color-content-default-brand-primary)"
: "var(--color-content-invert-primary, #000000)" // Black for inverse mode : "var(--color-content-invert-primary)"
: "transparent"; : "transparent";
// Label color // Label color
+11 -11
View File
@@ -8,7 +8,7 @@ function chipSurfaceStyle(
palette: ChipViewProps["palette"], palette: ChipViewProps["palette"],
size: ChipViewProps["size"], size: ChipViewProps["size"],
): CSSProperties { ): CSSProperties {
const borderWidth = size === "s" ? "1.25px" : "2px"; const borderWidth = size === "s" ? "1.25px" : "1.5px";
const reset: CSSProperties = { const reset: CSSProperties = {
appearance: "none", appearance: "none",
WebkitAppearance: "none", WebkitAppearance: "none",
@@ -64,14 +64,14 @@ function chipSurfaceStyle(
} }
if (state === "selected") { if (state === "selected") {
return paint( return paint(
"var(--color-surface-invert-brand-primary, #fefcc9)", "var(--color-surface-invert-brand-primary)",
"var(--color-content-invert-primary, #000)", "var(--color-content-invert-primary)",
"var(--color-border-default-brand-primary, #fdfaa8)", "var(--color-border-default-brand-primary)",
); );
} }
return paint( return paint(
"transparent", "transparent",
"var(--color-content-default-brand-primary, #fefcc9)", "var(--color-content-default-brand-primary)",
"var(--color-border-default-control)", "var(--color-border-default-control)",
); );
} }
@@ -185,7 +185,7 @@ function ChipView({
{onCheck && ( {onCheck && (
<button <button
type="button" type="button"
className="flex items-center justify-center p-[var(--space-150)] rounded-full hover:bg-[var(--color-surface-default-semi-opaque,rgba(0,0,0,0.1))] transition-colors disabled:opacity-50 disabled:cursor-not-allowed" className="flex items-center justify-center p-[var(--space-150)] rounded-full hover:bg-[var(--color-surface-default-semi-opaque)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
aria-label={confirmAriaLabel} aria-label={confirmAriaLabel}
disabled={!inputValue || !inputValue.trim()} disabled={!inputValue || !inputValue.trim()}
onClick={(event) => { onClick={(event) => {
@@ -202,7 +202,7 @@ function ChipView({
viewBox="0 0 12 12" viewBox="0 0 12 12"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className="text-[var(--color-content-default-brand-primary,#fefcc9)]" className="text-[var(--color-content-default-brand-primary)]"
> >
<path <path
d="M10 3L4.5 8.5L2 6" d="M10 3L4.5 8.5L2 6"
@@ -224,7 +224,7 @@ function ChipView({
onChange={(e) => onInputChange?.(e.target.value)} onChange={(e) => onInputChange?.(e.target.value)}
onKeyDown={onInputKeyDown} onKeyDown={onInputKeyDown}
placeholder={typeToAddPlaceholder} placeholder={typeToAddPlaceholder}
className="bg-transparent border-none outline-none flex-1 min-w-0 font-normal text-[color:var(--color-content-default-tertiary,#b4b4b4)] placeholder:text-[color:var(--color-content-default-tertiary,#b4b4b4)]" className="bg-transparent border-none outline-none flex-1 min-w-0 font-normal text-[color:var(--color-content-default-tertiary)] placeholder:text-[color:var(--color-content-default-tertiary)]"
style={{ style={{
fontSize: isSmall fontSize: isSmall
? "var(--sizing-300)" ? "var(--sizing-300)"
@@ -240,7 +240,7 @@ function ChipView({
{onClose && ( {onClose && (
<button <button
type="button" type="button"
className="flex items-center justify-center p-[var(--space-150)] rounded-full hover:bg-[var(--color-surface-default-semi-opaque,rgba(0,0,0,0.1))] transition-colors" className="flex items-center justify-center p-[var(--space-150)] rounded-full hover:bg-[var(--color-surface-default-semi-opaque)] transition-colors"
aria-label={closeAriaLabel} aria-label={closeAriaLabel}
onClick={(event) => { onClick={(event) => {
event.stopPropagation(); event.stopPropagation();
@@ -253,7 +253,7 @@ function ChipView({
viewBox="0 0 12 12" viewBox="0 0 12 12"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className="text-[var(--color-content-default-brand-primary,#fefcc9)]" className="text-[var(--color-content-default-brand-primary)]"
> >
<path <path
d="M9 3L3 9M3 3L9 9" d="M9 3L3 9M3 3L9 9"
@@ -309,7 +309,7 @@ function ChipView({
{onRemove && !isDisabled && ( {onRemove && !isDisabled && (
<button <button
type="button" type="button"
className="ml-[var(--space-050)] p-[var(--space-050)] rounded-full hover:bg-[var(--color-surface-default-semi-opaque,rgba(0,0,0,0.05))]" className="ml-[var(--space-050)] p-[var(--space-050)] rounded-full hover:bg-[var(--color-surface-default-semi-opaque)]"
aria-label={`Remove ${label}`} aria-label={`Remove ${label}`}
onClick={(event) => { onClick={(event) => {
event.stopPropagation(); event.stopPropagation();
@@ -59,7 +59,7 @@ function MultiSelectView({
{!formHeader || !label ? ( {!formHeader || !label ? (
countCaption.length > 0 ? ( countCaption.length > 0 ? (
<p className="w-full text-x-small-paragraph text-[color:var(--color-content-default-tertiary,#b4b4b4)]"> <p className="w-full text-x-small-paragraph text-[color:var(--color-content-default-tertiary)]">
{countCaption} {countCaption}
</p> </p>
) : null ) : null
@@ -133,7 +133,7 @@ function MultiSelectView({
className={ className={
!addButtonText !addButtonText
? // Circular button with border (Rule style) ? // Circular button with border (Rule style)
`${atLimit ? "cursor-not-allowed opacity-60" : "cursor-pointer hover:opacity-80"} bg-[var(--color-surface-default-transparent,rgba(0,0,0,0))] border-[1.25px] ${isInverse ? "border-[var(--color-border-default-primary,#141414)]" : "border-[var(--color-border-default-control)]"} border-solid flex items-center justify-center ${isSmall ? "size-[30px]" : "size-[40px]"} rounded-[var(--radius-full)] shrink-0 transition-opacity` `${atLimit ? "cursor-not-allowed opacity-60" : "cursor-pointer hover:opacity-80"} bg-[var(--color-surface-default-transparent)] border-[1.25px] ${isInverse ? "border-[var(--color-border-default-primary)]" : "border-[var(--color-border-default-control)]"} border-solid flex items-center justify-center ${isSmall ? "size-[30px]" : "size-[40px]"} rounded-[var(--radius-full)] shrink-0 transition-opacity`
: // Text add control (default palette: white label + brand “+”; inverse: inverse primary for both) : // Text add control (default palette: white label + brand “+”; inverse: inverse primary for both)
`${atLimit ? "cursor-not-allowed opacity-60" : "cursor-pointer hover:opacity-80"} flex items-center justify-center overflow-hidden rounded-[var(--radius-full)] shrink-0 transition-opacity ${ `${atLimit ? "cursor-not-allowed opacity-60" : "cursor-pointer hover:opacity-80"} flex items-center justify-center overflow-hidden rounded-[var(--radius-full)] shrink-0 transition-opacity ${
isSmall isSmall
@@ -151,8 +151,8 @@ function MultiSelectView({
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className={`shrink-0 ${ className={`shrink-0 ${
!addButtonText && isInverse !addButtonText && isInverse
? "text-[var(--color-content-invert-primary,black)]" ? "text-[var(--color-content-invert-primary)]"
: "text-[var(--color-content-default-brand-primary,#fefcc9)]" : "text-[var(--color-content-default-brand-primary)]"
}`} }`}
> >
<path <path
@@ -167,8 +167,8 @@ function MultiSelectView({
<span <span
className={`font-medium ${isSmall ? "text-[length:var(--sizing-300)] leading-[14px]" : "text-medium-label"} ${ className={`font-medium ${isSmall ? "text-[length:var(--sizing-300)] leading-[14px]" : "text-medium-label"} ${
isInverse isInverse
? "text-[color:var(--color-content-invert-primary,black)]" ? "text-[color:var(--color-content-invert-primary)]"
: "text-[color:var(--color-content-default-primary,white)]" : "text-[color:var(--color-content-default-primary)]"
}`} }`}
> >
{addButtonText} {addButtonText}
@@ -53,15 +53,15 @@ const RadioButtonContainer = ({
// Focus shows shadow (double ring: 2px white inner, 4px dark outer) // Focus shows shadow (double ring: 2px white inner, 4px dark outer)
// When checked, border is brand primary (but changes to invert tertiary on focus) // When checked, border is brand primary (but changes to invert tertiary on focus)
const defaultBorder = checked const defaultBorder = checked
? "border-[var(--color-border-default-brand-primary,#fdfaa8)]" ? "border-[var(--color-border-default-brand-primary)]"
: "border-[var(--color-border-default-control)]"; : "border-[var(--color-border-default-control)]";
// When focused and checked, border should be invert tertiary (#2d2d2d) per Figma // When focused and checked, border should be invert tertiary (#2d2d2d) per Figma
const focusBorder = checked const focusBorder = checked
? "focus:border-[var(--color-content-invert-tertiary,#2d2d2d)]" ? "focus:border-[var(--color-content-invert-tertiary)]"
: "focus:border-[var(--color-border-default-control)]"; : "focus:border-[var(--color-border-default-control)]";
return `${baseBox} bg-[var(--color-surface-default-primary)] border border-solid ${defaultBorder} hover:border-[var(--color-border-default-brand-primary,#fdfaa8)] ${focusBorder} focus:shadow-[var(--shadow-focus-invert)] focus:outline-none`; return `${baseBox} bg-[var(--color-surface-default-primary)] border border-solid ${defaultBorder} hover:border-[var(--color-border-default-brand-primary)] ${focusBorder} focus:shadow-[var(--shadow-focus-invert)] focus:outline-none`;
} }
// Inverse mode styles // Inverse mode styles
@@ -71,17 +71,17 @@ const RadioButtonContainer = ({
// Focus shows shadow (double ring: 2px dark inner, 4px white outer) // Focus shows shadow (double ring: 2px dark inner, 4px white outer)
// When checked, border is brand primary (but changes to white on focus) // When checked, border is brand primary (but changes to white on focus)
const defaultBorder = checked const defaultBorder = checked
? "border-[var(--color-border-default-brand-primary,#fdfaa8)]" ? "border-[var(--color-border-default-brand-primary)]"
: "border-[var(--color-border-invert-primary,white)]"; : "border-[var(--color-border-invert-primary)]";
// Hover border: inverse brand primary for both selected and unselected per Figma // Hover border: inverse brand primary for both selected and unselected per Figma
const hoverBorder = const hoverBorder =
"hover:border-[var(--color-border-invert-brand-primary,#6c6701)]"; "hover:border-[var(--color-border-invert-brand-primary)]";
// Focus border: when focused and checked, border should be white per Figma // Focus border: when focused and checked, border should be white per Figma
const focusBorder = checked const focusBorder = checked
? "focus:border-[var(--color-border-invert-primary,white)]" ? "focus:border-[var(--color-border-invert-primary)]"
: "focus:border-[var(--color-border-invert-primary,white)]"; : "focus:border-[var(--color-border-invert-primary)]";
return `${baseBox} bg-transparent border border-solid ${defaultBorder} ${hoverBorder} ${focusBorder} focus:shadow-[var(--shadow-focus-default)] focus:outline-none`; return `${baseBox} bg-transparent border border-solid ${defaultBorder} ${hoverBorder} ${focusBorder} focus:shadow-[var(--shadow-focus-default)] focus:outline-none`;
} }
@@ -39,9 +39,9 @@ export function RadioButtonView({
<div <div
className={`w-[16px] h-[16px] rounded-full transition-all duration-200 ${ className={`w-[16px] h-[16px] rounded-full transition-all duration-200 ${
checked && mode === "standard" checked && mode === "standard"
? "bg-[var(--color-content-default-brand-primary,#fefcc9)] group-hover:!bg-[var(--color-surface-default-brand-primary)]" ? "bg-[var(--color-content-default-brand-primary)] group-hover:!bg-[var(--color-surface-default-brand-primary)]"
: checked && mode === "inverse" : checked && mode === "inverse"
? "bg-[var(--color-content-default-primary,#000000)]" ? "bg-[var(--color-content-default-primary)]"
: "bg-transparent" : "bg-transparent"
}`} }`}
/> />
@@ -52,8 +52,8 @@ export function RadioGroupView({
<span <span
className={`text-small-paragraph ${ className={`text-small-paragraph ${
mode === "inverse" mode === "inverse"
? "text-[var(--color-content-invert-secondary,#1f1f1f)]" ? "text-[var(--color-content-invert-secondary)]"
: "text-[var(--color-content-default-secondary,#b4b4b4)]" : "text-[var(--color-content-default-secondary)]"
}`} }`}
> >
{option.subtext} {option.subtext}
@@ -109,7 +109,7 @@ export function SelectInputView({
.replace(/\s+/g, " "); .replace(/\s+/g, " ");
if (disabled) { if (disabled) {
return `${baseClasses} bg-[var(--color-surface-default-secondary)] text-[var(--color-content-invert-tertiary,#2d2d2d)] border-[var(--color-border-default-primary)] cursor-not-allowed opacity-40`; return `${baseClasses} bg-[var(--color-surface-default-secondary)] text-[var(--color-content-invert-tertiary)] border-[var(--color-border-default-primary)] cursor-not-allowed opacity-40`;
} }
if (error) { if (error) {
@@ -135,7 +135,7 @@ export function SelectInputView({
// Text color based on filled state // Text color based on filled state
const textColorClass = isFilled const textColorClass = isFilled
? "text-[var(--color-content-default-primary)]" ? "text-[var(--color-content-default-primary)]"
: "text-[var(--color-content-default-tertiary,#b4b4b4)]"; : "text-[var(--color-content-default-tertiary)]";
// Chevron icon // Chevron icon
const chevronClasses = `w-5 h-5 text-[var(--color-content-default-primary)] transition-transform duration-200 ${ const chevronClasses = `w-5 h-5 text-[var(--color-content-default-primary)] transition-transform duration-200 ${
@@ -151,7 +151,7 @@ export function SelectInputView({
{label} {label}
</label> </label>
{asterisk && ( {asterisk && (
<span className="text-[var(--color-content-default-negative-primary,#ea4845)] text-xx-small-label"> <span className="text-[var(--color-content-default-negative-primary)] text-xx-small-label">
* *
</span> </span>
)} )}
@@ -167,7 +167,7 @@ export function SelectInputView({
)} )}
</div> </div>
{textOptional && ( {textOptional && (
<span className="text-[var(--color-content-default-tertiary,#b4b4b4)] text-xx-small-paragraph"> <span className="text-[var(--color-content-default-tertiary)] text-xx-small-paragraph">
Optional text Optional text
</span> </span>
)} )}
@@ -102,7 +102,7 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
if (disabled) { if (disabled) {
return { return {
input: input:
"bg-[var(--color-surface-default-secondary)] text-[var(--color-content-invert-tertiary,#2d2d2d)] border border-solid border-[var(--color-border-default-primary)] cursor-not-allowed", "bg-[var(--color-surface-default-secondary)] text-[var(--color-content-invert-tertiary)] border border-solid border-[var(--color-border-default-primary)] cursor-not-allowed",
label: "text-[var(--color-content-default-secondary)]", label: "text-[var(--color-content-default-secondary)]",
inputWrapper: "relative", inputWrapper: "relative",
focusRing: "", focusRing: "",
@@ -171,7 +171,7 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
const inputClasses = ` const inputClasses = `
w-full transition-all duration-200 ease-in-out w-full transition-all duration-200 ease-in-out
focus:outline-none focus:ring-0 focus:outline-none focus:ring-0
placeholder:text-[var(--color-content-default-tertiary,#b4b4b4)] placeholder:text-[var(--color-content-default-tertiary)]
${sizeStyles.input} ${sizeStyles.input}
${stateStyles.input} ${stateStyles.input}
${className} ${className}
@@ -180,7 +180,7 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
// Text color for filled text (placeholder color is handled above) // Text color for filled text (placeholder color is handled above)
const textColorClass = isFilled const textColorClass = isFilled
? "text-[var(--color-content-default-primary)]" ? "text-[var(--color-content-default-primary)]"
: "text-[var(--color-content-default-tertiary,#b4b4b4)]"; : "text-[var(--color-content-default-tertiary)]";
// Form field handlers with disabled state handling // Form field handlers with disabled state handling
const { handleChange, handleBlur } = useFormField<HTMLInputElement>( const { handleChange, handleBlur } = useFormField<HTMLInputElement>(
@@ -20,22 +20,22 @@ function UploadView({
// Button styles based on active state // Button styles based on active state
const buttonBgClass = isActive const buttonBgClass = isActive
? "bg-[var(--color-surface-invert-primary,white)]" ? "bg-[var(--color-surface-invert-primary)]"
: "bg-[var(--color-surface-default-secondary,#141414)]"; : "bg-[var(--color-surface-default-secondary)]";
const buttonTextColor = isActive const buttonTextColor = isActive
? "text-[color:var(--color-content-invert-primary,black)]" ? "text-[color:var(--color-content-invert-primary)]"
: "text-[color:var(--color-content-invert-tertiary,#2d2d2d)]"; : "text-[color:var(--color-content-invert-tertiary)]";
// Description text color based on active state // Description text color based on active state
const descriptionTextColor = isActive const descriptionTextColor = isActive
? "text-[color:var(--color-content-default-primary,white)]" ? "text-[color:var(--color-content-default-primary)]"
: "text-[color:var(--color-content-default-tertiary,#b4b4b4)]"; : "text-[color:var(--color-content-default-tertiary)]";
// Icon color based on active state // Icon color based on active state
const iconColor = isActive const iconColor = isActive
? "text-[color:var(--color-content-invert-primary,black)]" ? "text-[color:var(--color-content-invert-primary)]"
: "text-[color:var(--color-content-invert-tertiary,#2d2d2d)]"; : "text-[color:var(--color-content-invert-tertiary)]";
return ( return (
<div <div
@@ -55,7 +55,7 @@ function UploadView({
{/* Upload container — max width per create-flow spec */} {/* Upload container — max width per create-flow spec */}
<div <div
className={`bg-[var(--color-surface-default-secondary,#141414)] mx-auto flex w-full max-w-[474px] shrink-0 items-center justify-center gap-[24px] rounded-[var(--radius-200)] px-[var(--space-600)] py-[var(--space-1200)] ${ className={`bg-[var(--color-surface-default-secondary)] mx-auto flex w-full max-w-[474px] shrink-0 items-center justify-center gap-[24px] rounded-[var(--radius-200)] px-[var(--space-600)] py-[var(--space-1200)] ${
state === "error" state === "error"
? "border border-[var(--color-border-default-utility-negative)]" ? "border border-[var(--color-border-default-utility-negative)]"
: "" : ""
-3
View File
@@ -216,9 +216,6 @@ const Footer = memo(({ siteOrigin = PRODUCTION_SITE_ORIGIN }: { siteOrigin?: str
<Link href={t("legal.termsOfServiceHref")} className={legalLinkClass}> <Link href={t("legal.termsOfServiceHref")} className={legalLinkClass}>
{t("legal.termsOfService")} {t("legal.termsOfService")}
</Link> </Link>
<Link href={t("legal.cookiesSettingsHref")} className={legalLinkClass}>
{t("legal.cookiesSettings")}
</Link>
</div> </div>
</div> </div>
</div> </div>
@@ -63,22 +63,22 @@ const MenuItemContainer = memo<MenuItemProps>(
const defaultModeStyles: Record<"default" | "hover" | "selected", string> = const defaultModeStyles: Record<"default" | "hover" | "selected", string> =
{ {
default: default:
"bg-transparent text-[var(--color-content-default-brand-primary,#fefcc9)] hover:bg-[var(--color-gray-800)] hover:text-[var(--color-content-default-brand-primary,#fefcc9)]", "bg-transparent text-[var(--color-content-default-brand-primary)] hover:bg-[var(--color-gray-800)] hover:text-[var(--color-content-default-brand-primary)]",
hover: hover:
"bg-[var(--color-gray-800)] text-[var(--color-content-default-brand-primary,#fefcc9)]", "bg-[var(--color-gray-800)] text-[var(--color-content-default-brand-primary)]",
selected: selected:
"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)] text-[var(--color-content-default-brand-primary)] bg-transparent hover:bg-[var(--color-gray-800)]",
}; };
// State styles for Inverse mode (black text on yellow HeaderTab / inverse surfaces) // 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-invert-primary,black)] hover:bg-[var(--color-surface-invert-brand-secondary)] hover:text-[var(--color-content-invert-primary,black)]", "bg-transparent text-[var(--color-content-invert-primary)] hover:bg-[var(--color-surface-invert-brand-secondary)] hover:text-[var(--color-content-invert-primary)]",
hover: hover:
"bg-[var(--color-surface-invert-brand-secondary)] text-[var(--color-content-invert-primary,black)]", "bg-[var(--color-surface-invert-brand-secondary)] text-[var(--color-content-invert-primary)]",
selected: selected:
"border border-[var(--color-border-default-primary,#141414)] text-[var(--color-content-invert-primary,black)] bg-transparent hover:bg-[var(--color-surface-invert-brand-secondary)]", "border border-[var(--color-border-default-primary)] text-[var(--color-content-invert-primary)] bg-transparent hover:bg-[var(--color-surface-invert-brand-secondary)]",
}; };
// Get state styles based on mode // Get state styles based on mode
@@ -33,6 +33,7 @@ const FeatureGridContainer = memo<FeatureGridProps>(
), ),
descriptionClassName: "text-[var(--color-content-invert-brand-lime)]", descriptionClassName: "text-[var(--color-content-invert-brand-lime)]",
iconSrc: getAssetPath(featureIconPath("arrow-left-right")), iconSrc: getAssetPath(featureIconPath("arrow-left-right")),
href: "/how-it-works",
}, },
{ {
backgroundColor: "bg-[var(--color-surface-invert-brand-rust)]", backgroundColor: "bg-[var(--color-surface-invert-brand-rust)]",
@@ -42,6 +43,7 @@ const FeatureGridContainer = memo<FeatureGridProps>(
), ),
descriptionClassName: "text-[var(--color-content-invert-brand-rust)]", descriptionClassName: "text-[var(--color-content-invert-brand-rust)]",
iconSrc: getAssetPath(featureIconPath("door-open")), iconSrc: getAssetPath(featureIconPath("door-open")),
href: "/blog/integrating-new-members-without-dilution",
}, },
{ {
backgroundColor: "bg-[var(--color-surface-invert-brand-teal)]", backgroundColor: "bg-[var(--color-surface-invert-brand-teal)]",
@@ -10,6 +10,7 @@ export interface Feature {
description: string; description: string;
descriptionClassName: string; descriptionClassName: string;
iconSrc: string; iconSrc: string;
href?: string;
} }
export interface FeatureGridViewProps extends FeatureGridProps { export interface FeatureGridViewProps extends FeatureGridProps {
@@ -1,8 +1,35 @@
"use client"; "use client";
import Link from "next/link";
import { useTranslation } from "../../../contexts/MessagesContext"; import { useTranslation } from "../../../contexts/MessagesContext";
import ContentLockup from "../../type/ContentLockup"; import ContentLockup from "../../type/ContentLockup";
import type { FeatureGridViewProps } from "./FeatureGrid.types"; import type { Feature, FeatureGridViewProps } from "./FeatureGrid.types";
function FeatureTileBody({ feature }: { feature: Feature }) {
return (
<>
<div className="size-[40px] shrink-0 overflow-clip">
{/* eslint-disable-next-line @next/next/no-img-element -- decorative Figma glyph */}
<img
src={feature.iconSrc}
alt=""
width={40}
height={40}
className="size-full object-contain"
role="presentation"
/>
</div>
<div className="flex w-full flex-col gap-[var(--space-200)]">
<p className="font-semibold text-[17px] leading-[22px] text-[var(--color-content-invert-primary)]">
{feature.title}
</p>
<p className={`text-small-paragraph ${feature.descriptionClassName}`}>
{feature.description}
</p>
</div>
</>
);
}
/** Figma **Section / Feature-Grid** [18632:10668](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=18632-10668&m=dev). */ /** Figma **Section / Feature-Grid** [18632:10668](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=18632-10668&m=dev). */
function FeatureGridView({ function FeatureGridView({
@@ -41,34 +68,25 @@ function FeatureGridView({
</div> </div>
<div className="grid w-full grid-cols-1 gap-[var(--space-200)] md:grid-cols-2 md:gap-[var(--space-400)] lg:flex-1"> <div className="grid w-full grid-cols-1 gap-[var(--space-200)] md:grid-cols-2 md:gap-[var(--space-400)] lg:flex-1">
{features.map((feature) => ( {features.map((feature) => {
<div const tileClassName = `flex flex-col gap-[var(--space-400)] p-[var(--space-600)] ${feature.backgroundColor} rounded-[var(--radius-200)] md:rounded-[var(--radius-400)] lg:rounded-[var(--radius-500)]`;
key={feature.title} if (feature.href) {
className={`flex flex-col gap-[var(--space-400)] p-[var(--space-600)] ${feature.backgroundColor} rounded-[var(--radius-200)] md:rounded-[var(--radius-400)] lg:rounded-[var(--radius-500)]`} return (
> <Link
<div className="size-[40px] shrink-0 overflow-clip"> key={feature.title}
{/* eslint-disable-next-line @next/next/no-img-element -- decorative Figma glyph */} href={feature.href}
<img className={`${tileClassName} no-underline touch-manipulation [-webkit-tap-highlight-color:transparent] hover:opacity-90 focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus-invert)]`}
src={feature.iconSrc}
alt=""
width={40}
height={40}
className="size-full object-contain"
role="presentation"
/>
</div>
<div className="flex w-full flex-col gap-[var(--space-200)]">
<p className="font-semibold text-[17px] leading-[22px] text-[var(--color-content-invert-primary)]">
{feature.title}
</p>
<p
className={`text-small-paragraph ${feature.descriptionClassName}`}
> >
{feature.description} <FeatureTileBody feature={feature} />
</p> </Link>
);
}
return (
<div key={feature.title} className={tileClassName}>
<FeatureTileBody feature={feature} />
</div> </div>
</div> );
))} })}
</div> </div>
</div> </div>
</div> </div>
@@ -75,7 +75,7 @@ function QuoteBlockView({
role="region" role="region"
> >
<div <div
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(--space-500)] bg-[var(--color-surface-invert-brand-primary,#fefcc9)] px-[var(--space-800)] py-[var(--space-1200)] md:px-[var(--space-1800)] md:py-[var(--space-2400)]" 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(--space-500)] bg-[var(--color-surface-invert-brand-primary)] px-[var(--space-800)] py-[var(--space-1200)] md:px-[var(--space-1800)] md:py-[var(--space-2400)]"
> >
<QuoteStatementDecor /> <QuoteStatementDecor />
<div className="relative z-10 flex w-full min-w-0 shrink-0 flex-col items-center text-center"> <div className="relative z-10 flex w-full min-w-0 shrink-0 flex-col items-center text-center">
+4 -3
View File
@@ -2,16 +2,17 @@ import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import messages from "../messages/en/index"; import messages from "../messages/en/index";
import { getTranslation } from "../lib/i18n/getTranslation"; import { getTranslation } from "../lib/i18n/getTranslation";
import { NO_INDEX_ROBOTS } from "../lib/siteMetadata"; import { NO_INDEX_ROBOTS, routeMetadata } from "../lib/siteMetadata";
import { getGovernanceTemplateCatalogEntry } from "../lib/templates/governanceTemplateCatalog"; import { getGovernanceTemplateCatalogEntry } from "../lib/templates/governanceTemplateCatalog";
import Icon from "./components/asset/icon"; import Icon from "./components/asset/icon";
import Button from "./components/buttons/Button"; import Button from "./components/buttons/Button";
import HeroDecor from "./components/sections/HeroBanner/HeroDecor"; import HeroDecor from "./components/sections/HeroBanner/HeroDecor";
export const metadata: Metadata = { export const metadata: Metadata = routeMetadata("/404", {
title: messages.metadata.notFound.title, title: messages.metadata.notFound.title,
description: messages.metadata.notFound.description,
robots: NO_INDEX_ROBOTS, robots: NO_INDEX_ROBOTS,
}; });
const NOT_FOUND_TEMPLATE_SLUGS = [ const NOT_FOUND_TEMPLATE_SLUGS = [
"consensus", "consensus",
+83 -80
View File
@@ -202,14 +202,15 @@
/* Grays & alpha */ /* Grays & alpha */
--color-gray-000: #ffffff; --color-gray-000: #ffffff;
--color-gray-050: #f6f6f6; --color-gray-050: #f6f6f6;
--color-gray-100: #e5e5e5; --color-gray-100: #e4e4e4;
--color-gray-200: #d2d2d2; --color-gray-150: #d2d2d2;
--color-gray-300: #b4b4b4; --color-gray-200: #c2c2c2;
--color-gray-400: #8f8f8f; --color-gray-300: #a4a4a4;
--color-gray-500: #666666; --color-gray-400: #8a8a8a;
--color-gray-600: #464646; --color-gray-500: #717171;
--color-gray-700: #2d2d2d; --color-gray-600: #5b5b5b;
--color-gray-800: #1f1f1f; --color-gray-700: #464646;
--color-gray-800: #303030;
--color-gray-900: #141414; --color-gray-900: #141414;
--color-gray-1000: #000000; --color-gray-1000: #000000;
@@ -244,16 +245,16 @@
/* Lavender */ /* Lavender */
--color-lavender-lavender0: #f0ebff; --color-lavender-lavender0: #f0ebff;
--color-lavender-lavender50: #e8defe; --color-lavender-lavender50: #e8defe;
--color-lavender-lavender100: #e0d1fd; --color-lavender-lavender100: #dccdf9;
--color-lavender-lavender150: #d7c3f9; --color-lavender-lavender150: #d1bdf3;
--color-lavender-lavender200: #ceb5f6; --color-lavender-lavender200: #c6adee;
--color-lavender-lavender300: #bb97ea; --color-lavender-lavender300: #b490e2;
--color-lavender-lavender400: #a578d9; --color-lavender-lavender400: #a275d5;
--color-lavender-lavender500: #8d5ac4; --color-lavender-lavender500: #8f5cc6;
--color-lavender-lavender600: #743ea9; --color-lavender-lavender600: #7c47b2;
--color-lavender-lavender700: #5a278c; --color-lavender-lavender700: #67359b;
--color-lavender-lavender800: #41146c; --color-lavender-lavender800: #50277e;
--color-lavender-lavender900: #2b074d; --color-lavender-lavender900: #38185c;
--color-lavender-lavender1000: #1a0033; --color-lavender-lavender1000: #1a0033;
/* Lime */ /* Lime */
@@ -274,61 +275,61 @@
/* Magenta */ /* Magenta */
--color-magenta-magenta0: #ffebf0; --color-magenta-magenta0: #ffebf0;
--color-magenta-magenta50: #fedee7; --color-magenta-magenta50: #fedee7;
--color-magenta-magenta100: #fdd1dd; --color-magenta-magenta100: #f8ccd8;
--color-magenta-magenta150: #f9c3d2; --color-magenta-magenta150: #f0bbca;
--color-magenta-magenta200: #f6b5c7; --color-magenta-magenta200: #eaaabc;
--color-magenta-magenta300: #ea97ad; --color-magenta-magenta300: #dd8ba1;
--color-magenta-magenta400: #d97892; --color-magenta-magenta400: #ce6f89;
--color-magenta-magenta500: #c45a76; --color-magenta-magenta500: #be5571;
--color-magenta-magenta600: #a93e5b; --color-magenta-magenta600: #aa3f5b;
--color-magenta-magenta700: #8c2742; --color-magenta-magenta700: #922d47;
--color-magenta-magenta800: #6c142b; --color-magenta-magenta800: #761f33;
--color-magenta-magenta900: #4d071a; --color-magenta-magenta900: #571222;
--color-magenta-magenta1000: #33000e; --color-magenta-magenta1000: #33000e;
/* Red */ /* Red */
--color-red-red0: #ffebf1; --color-red-red0: #ffebf1;
--color-red-red50: #fec9d6; --color-red-red50: #fec9d6;
--color-red-red100: #fda8b7; --color-red-red100: #ffb6c2;
--color-red-red150: #f98c98; --color-red-red150: #ffa4ac;
--color-red-red200: #f66f78; --color-red-red200: #ff9296;
--color-red-red300: #ea4845; --color-red-red300: #ff6a62;
--color-red-red400: #d93529; --color-red-red400: #ef4c3d;
--color-red-red500: #c42916; --color-red-red500: #d33a26;
--color-red-red600: #a9200b; --color-red-red600: #b62e1a;
--color-red-red700: #8c1905; --color-red-red700: #972613;
--color-red-red800: #6c1301; --color-red-red800: #771f0d;
--color-red-red900: #4d0d00; --color-red-red900: #571707;
--color-red-red1000: #330800; --color-red-red1000: #330800;
/* Royal Blue */ /* Royal Blue */
--color-royal-blue-royal-blue0: #ebecff; --color-royal-blue-royal-blue0: #ebecff;
--color-royal-blue-royal-blue50: #c9cdfe; --color-royal-blue-royal-blue50: #c9cdfe;
--color-royal-blue-royal-blue100: #a8adfd; --color-royal-blue-royal-blue100: #b9bfff;
--color-royal-blue-royal-blue150: #8c91f9; --color-royal-blue-royal-blue150: #aab1ff;
--color-royal-blue-royal-blue200: #6f74f6; --color-royal-blue-royal-blue200: #99a4ff;
--color-royal-blue-royal-blue300: #4548ea; --color-royal-blue-royal-blue300: #778cff;
--color-royal-blue-royal-blue400: #2b29d9; --color-royal-blue-royal-blue400: #5674ff;
--color-royal-blue-royal-blue500: #1c16c4; --color-royal-blue-royal-blue500: #3a5afe;
--color-royal-blue-royal-blue600: #140ba9; --color-royal-blue-royal-blue600: #2d4adb;
--color-royal-blue-royal-blue700: #0f058c; --color-royal-blue-royal-blue700: #233bb6;
--color-royal-blue-royal-blue800: #0c016c; --color-royal-blue-royal-blue800: #1d2c8f;
--color-royal-blue-royal-blue900: #0a004d; --color-royal-blue-royal-blue900: #171b65;
--color-royal-blue-royal-blue1000: #080033; --color-royal-blue-royal-blue1000: #080033;
/* Rust */ /* Rust */
--color-rust-rust0: #ffedeb; --color-rust-rust0: #ffedeb;
--color-rust-rust50: #fed8d4; --color-rust-rust50: #fed8d4;
--color-rust-rust100: #fdc4bd; --color-rust-rust100: #ffc6bf;
--color-rust-rust150: #f9b0a6; --color-rust-rust150: #feb4aa;
--color-rust-rust200: #f69c8f; --color-rust-rust200: #fda395;
--color-rust-rust300: #ea7a65; --color-rust-rust300: #f4836e;
--color-rust-rust400: #d96043; --color-rust-rust400: #e46a4c;
--color-rust-rust500: #c44e29; --color-rust-rust500: #cd5632;
--color-rust-rust600: #a94417; --color-rust-rust600: #b04a1f;
--color-rust-rust700: #8c3d0b; --color-rust-rust700: #904111;
--color-rust-rust800: #6c3604; --color-rust-rust800: #703909;
--color-rust-rust900: #4d2c01; --color-rust-rust900: #512f05;
--color-rust-rust1000: #332100; --color-rust-rust1000: #332100;
/* Teal */ /* Teal */
@@ -392,25 +393,25 @@
--color-border-invert-warning-primary: var(--color-yellow-yellow600); --color-border-invert-warning-primary: var(--color-yellow-yellow600);
--color-border-invert-negative-primary: var(--color-red-red300); --color-border-invert-negative-primary: var(--color-red-red300);
/* Link — nearest palette step for each off-ramp hex */ /* Link — Navigation / Link binds content tokens */
--color-link-primary: var(--color-gray-1000); --color-link-primary: var(--color-content-invert-primary);
--color-link-primary-hover: var(--color-gray-500); --color-link-primary-hover: var(--color-content-invert-quaternary);
--color-link-primary-focus: var(--color-gray-500); --color-link-primary-focus: var(--color-content-invert-quaternary);
--color-link-primary-active: var(--color-gray-500); --color-link-primary-active: var(--color-content-invert-quaternary);
--color-link-secondary: var(--color-gray-500); --color-link-secondary: var(--color-content-invert-quaternary);
--color-link-secondary-hover: var(--color-gray-700); --color-link-secondary-hover: var(--color-content-invert-tertiary);
--color-link-secondary-focus: var(--color-gray-700); --color-link-secondary-focus: var(--color-content-invert-tertiary);
--color-link-secondary-active: var(--color-gray-700); --color-link-secondary-active: var(--color-content-invert-tertiary);
--color-link-invert-primary: var(--color-gray-000); --color-link-invert-primary: var(--color-content-default-primary);
--color-link-invert-primary-hover: var(--color-gray-300); --color-link-invert-primary-hover: var(--color-content-default-tertiary);
--color-link-invert-primary-focus: var(--color-gray-300); --color-link-invert-primary-focus: var(--color-content-default-tertiary);
--color-link-invert-primary-active: var(--color-gray-300); --color-link-invert-primary-active: var(--color-content-default-tertiary);
--color-link-invert-secondary: var(--color-gray-300); --color-link-invert-secondary: var(--color-content-default-tertiary);
--color-link-invert-secondary-hover: var(--color-gray-050); --color-link-invert-secondary-hover: var(--color-content-default-primary);
--color-link-invert-secondary-focus: var(--color-gray-050); --color-link-invert-secondary-focus: var(--color-content-default-primary);
--color-link-invert-secondary-active: var(--color-gray-050); --color-link-invert-secondary-active: var(--color-content-default-primary);
--color-border-link-focus: var(--color-gray-1000); --color-border-link-focus: var(--color-border-default-primary);
--color-border-link-invert-focus: var(--color-gray-000); --color-border-link-invert-focus: var(--color-border-invert-primary);
/* Content */ /* Content */
--color-content-default-brand-accent: var(--color-opacity-700); --color-content-default-brand-accent: var(--color-opacity-700);
@@ -443,10 +444,12 @@
--color-content-invert-primary: var(--color-gray-1000); --color-content-invert-primary: var(--color-gray-1000);
--color-content-invert-secondary: var(--color-gray-800); --color-content-invert-secondary: var(--color-gray-800);
--color-content-invert-tertiary: var(--color-gray-700); --color-content-invert-tertiary: var(--color-gray-700);
--color-content-invert-quaternary: var(--color-gray-500);
--color-content-invert-brand-primary: var(--color-yellow-yellow900); --color-content-invert-brand-primary: var(--color-yellow-yellow900);
--color-content-invert-brand-secondary: var(--color-yellow-yellow800); --color-content-invert-brand-secondary: var(--color-yellow-yellow800);
--color-content-invert-brand-lime: var(--color-lime-lime800); --color-content-invert-brand-lime: var(--color-lime-lime800);
--color-content-invert-brand-royal: var(--color-royal-blue-royal-blue400); --color-content-invert-brand-royal: var(--color-royal-blue-royal-blue700);
--color-content-invert-brand-navy: var(--color-royal-blue-royal-blue1000);
--color-content-invert-brand-teal: var(--color-teal-teal700); --color-content-invert-brand-teal: var(--color-teal-teal700);
--color-content-invert-brand-rust: var(--color-rust-rust700); --color-content-invert-brand-rust: var(--color-rust-rust700);
--color-content-invert-brand-lavender: var(--color-lavender-lavender700); --color-content-invert-brand-lavender: var(--color-lavender-lavender700);
@@ -454,7 +457,7 @@
--color-content-invert-brand-red: var(--color-red-red700); --color-content-invert-brand-red: var(--color-red-red700);
--color-content-invert-positive-primary: var(--color-kiwi-kiwi800); --color-content-invert-positive-primary: var(--color-kiwi-kiwi800);
--color-content-invert-warning-primary: var(--color-yellow-yellow800); --color-content-invert-warning-primary: var(--color-yellow-yellow800);
--color-content-invert-negative-primary: var(--color-red-red500); --color-content-invert-negative-primary: var(--color-red-red600);
/* Surface */ /* Surface */
--color-surface-default-brand-red: var(--color-red-red700); --color-surface-default-brand-red: var(--color-red-red700);
@@ -463,7 +466,7 @@
--color-surface-default-brand-lime: var(--color-lime-lime800); --color-surface-default-brand-lime: var(--color-lime-lime800);
--color-surface-default-brand-primary: var(--color-yellow-yellow1000); --color-surface-default-brand-primary: var(--color-yellow-yellow1000);
--color-surface-default-brand-secondary: var(--color-yellow-yellow800); --color-surface-default-brand-secondary: var(--color-yellow-yellow800);
--color-surface-default-brand-royal: var(--color-royal-blue-royal-blue600); --color-surface-default-brand-royal: var(--color-royal-blue-royal-blue700);
--color-surface-default-brand-rust: var(--color-rust-rust700); --color-surface-default-brand-rust: var(--color-rust-rust700);
--color-surface-default-brand-teal: var(--color-teal-teal700); --color-surface-default-brand-teal: var(--color-teal-teal700);
--color-surface-default-positive-secondary: var(--color-kiwi-kiwi1000); --color-surface-default-positive-secondary: var(--color-kiwi-kiwi1000);
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"_comment": "FeatureGrid component defaults (shared across pages). Explore the patterns goes to /learn; feature cards stay non-interactive until destinations are decided.", "_comment": "FeatureGrid component defaults (shared across pages). Explore the patterns goes to /templates. Feature cards link only when a matching page exists.",
"linkText": "Explore the patterns", "linkText": "Explore the patterns",
"linkHref": "/learn", "linkHref": "/templates",
"ariaLabel": "Feature tools and services", "ariaLabel": "Feature tools and services",
"features": { "features": {
"decisionMaking": { "decisionMaking": {
+1 -3
View File
@@ -31,9 +31,7 @@
"privacyPolicy": "Privacy Policy", "privacyPolicy": "Privacy Policy",
"privacyPolicyHref": "/privacy", "privacyPolicyHref": "/privacy",
"termsOfService": "Terms of Service", "termsOfService": "Terms of Service",
"termsOfServiceHref": "/terms", "termsOfServiceHref": "/terms"
"cookiesSettings": "Cookies Settings",
"cookiesSettingsHref": "/cookies"
}, },
"license": { "license": {
"_comment": "Replaces Figma's '© All right reserved'. Covers user-facing content (guides, template copy, marketing) per README — not GPL-3.0 application source. Published user-authored rules are a separate grant.", "_comment": "Replaces Figma's '© All right reserved'. Covers user-facing content (guides, template copy, marketing) per README — not GPL-3.0 application source. Published user-authored rules are a separate grant.",
+10 -6
View File
@@ -7,8 +7,8 @@
"templatesAlt": "CommunityRule governance templates" "templatesAlt": "CommunityRule governance templates"
}, },
"home": { "home": {
"title": "Build structure before you need it — CommunityRule", "title": "CommunityRule | Write down how your community works",
"description": "Strong communities agree on four things early. They settle how they talk, who belongs, how they decide and what happens when they disagree. Write yours down together.", "description": "Write down how your community talks, who belongs, how it decides and what happens when it disagrees. Free for every community.",
"keywords": [ "keywords": [
"community", "community",
"governance", "governance",
@@ -47,16 +47,20 @@
"fallbackDescription": "A blog post from our community." "fallbackDescription": "A blog post from our community."
}, },
"login": { "login": {
"title": "Log in" "title": "Log in",
"description": "Enter your email and we'll send you a magic link to sign in. No password needed!"
}, },
"profile": { "profile": {
"title": "Profile" "title": "Profile",
"description": "Manage your CommunityRules, saved progress, and account."
}, },
"monitor": { "monitor": {
"title": "Monitor" "title": "Monitor",
"description": "Real-time monitoring of Core Web Vitals and performance metrics for Community Rule 3.0"
}, },
"notFound": { "notFound": {
"title": "Page not found" "title": "Page not found",
"description": "Looks like this page didn't make it to a consensus. It may have moved, been removed, or never existed."
}, },
"useCases": { "useCases": {
"title": "Use cases", "title": "Use cases",
+16 -6
View File
@@ -47,22 +47,32 @@ describe("FeatureGrid (behavioral tests)", () => {
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
}); });
it("renders all four feature cards as static tiles", () => { it("links the two features that have pages and leaves the others as tiles", () => {
render(<FeatureGrid title="Test" subtitle="Test" />); render(<FeatureGrid title="Test" subtitle="Test" />);
expect(screen.getByText("Choose how you decide")).toBeInTheDocument(); expect(screen.getByText("Choose how you decide")).toBeInTheDocument();
expect(screen.getByText("Name your values")).toBeInTheDocument();
expect(screen.getByText("Define membership")).toBeInTheDocument();
expect(screen.getByText("Plan for conflict")).toBeInTheDocument(); expect(screen.getByText("Plan for conflict")).toBeInTheDocument();
expect( expect(
screen.queryByRole("link", { name: "Choose how you decide" }), screen.queryByRole("link", { name: /Choose how you decide/ }),
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
expect(
screen.queryByRole("link", { name: /Plan for conflict/ }),
).not.toBeInTheDocument();
expect(
screen.getByRole("link", { name: /Name your values/ }),
).toHaveAttribute("href", "/how-it-works");
expect(
screen.getByRole("link", { name: /Define membership/ }),
).toHaveAttribute(
"href",
"/blog/integrating-new-members-without-dilution",
);
}); });
it("sends Explore the patterns to the Learn page", () => { it("sends Explore the patterns to the templates page", () => {
render(<FeatureGrid title="Test" subtitle="Test" />); render(<FeatureGrid title="Test" subtitle="Test" />);
expect( expect(
screen.getByRole("link", { name: "Explore the patterns" }), screen.getByRole("link", { name: "Explore the patterns" }),
).toHaveAttribute("href", "/learn"); ).toHaveAttribute("href", "/templates");
}); });
it("does not apply a focus ring to the entire grid shell", () => { it("does not apply a focus ring to the entire grid shell", () => {
+3 -3
View File
@@ -110,7 +110,7 @@ describe("Footer (behavioral tests)", () => {
expect(screen.queryByText(/all right reserved/i)).not.toBeInTheDocument(); expect(screen.queryByText(/all right reserved/i)).not.toBeInTheDocument();
}); });
it("renders legal links to privacy, terms, and cookies pages", () => { it("renders legal links to privacy and terms pages", () => {
render(<Footer />); render(<Footer />);
expect( expect(
screen.getAllByRole("link", { name: "Privacy Policy" })[0], screen.getAllByRole("link", { name: "Privacy Policy" })[0],
@@ -119,7 +119,7 @@ describe("Footer (behavioral tests)", () => {
screen.getAllByRole("link", { name: "Terms of Service" })[0], screen.getAllByRole("link", { name: "Terms of Service" })[0],
).toHaveAttribute("href", "/terms"); ).toHaveAttribute("href", "/terms");
expect( expect(
screen.getAllByRole("link", { name: "Cookies Settings" })[0], screen.queryByRole("link", { name: "Cookies Settings" }),
).toHaveAttribute("href", "/cookies"); ).not.toBeInTheDocument();
}); });
}); });
+4 -4
View File
@@ -37,7 +37,7 @@ describe("Upload (behavioral tests)", () => {
render(<Upload label="Upload" />); render(<Upload label="Upload" />);
const button = screen.getByRole("button", { name: /upload/i }); const button = screen.getByRole("button", { name: /upload/i });
expect(button).toHaveClass( expect(button).toHaveClass(
"bg-[var(--color-surface-invert-primary,white)]", "bg-[var(--color-surface-invert-primary)]",
); );
}); });
@@ -45,7 +45,7 @@ describe("Upload (behavioral tests)", () => {
render(<Upload label="Upload" active={false} />); render(<Upload label="Upload" active={false} />);
const button = screen.getByRole("button", { name: /upload/i }); const button = screen.getByRole("button", { name: /upload/i });
expect(button).toHaveClass( expect(button).toHaveClass(
"bg-[var(--color-surface-default-secondary,#141414)]", "bg-[var(--color-surface-default-secondary)]",
); );
}); });
@@ -95,7 +95,7 @@ describe("Upload (behavioral tests)", () => {
); );
const descriptionContainer = descriptionText.parentElement; const descriptionContainer = descriptionText.parentElement;
expect(descriptionContainer).toHaveClass( expect(descriptionContainer).toHaveClass(
"text-[color:var(--color-content-default-primary,white)]", "text-[color:var(--color-content-default-primary)]",
); );
}); });
@@ -106,7 +106,7 @@ describe("Upload (behavioral tests)", () => {
); );
const descriptionContainer = descriptionText.parentElement; const descriptionContainer = descriptionText.parentElement;
expect(descriptionContainer).toHaveClass( expect(descriptionContainer).toHaveClass(
"text-[color:var(--color-content-default-tertiary,#b4b4b4)]", "text-[color:var(--color-content-default-tertiary)]",
); );
}); });
}); });
+13 -2
View File
@@ -129,13 +129,24 @@ test.describe("Critical User Journeys", () => {
featureSection.locator("text=Plan for conflict"), featureSection.locator("text=Plan for conflict"),
).toBeVisible(); ).toBeVisible();
// Feature tiles are presentational — only the lockup "Explore the patterns" is a link
await expect( await expect(
featureSection.locator('a[href="#decision-making"]'), featureSection.locator('a[href="#decision-making"]'),
).toHaveCount(0); ).toHaveCount(0);
await expect(
featureSection.getByRole("link", { name: "Choose how you decide" }),
).toHaveCount(0);
await expect(
featureSection.getByRole("link", { name: "Plan for conflict" }),
).toHaveCount(0);
await expect(
featureSection.getByRole("link", { name: "Name your values" }),
).toHaveAttribute("href", "/how-it-works");
await expect(
featureSection.getByRole("link", { name: "Define membership" }),
).toHaveAttribute("href", "/blog/integrating-new-members-without-dilution");
await expect( await expect(
featureSection.getByRole("link", { name: "Explore the patterns" }), featureSection.getByRole("link", { name: "Explore the patterns" }),
).toHaveAttribute("href", "/learn"); ).toHaveAttribute("href", "/templates");
}); });
test("header navigation functionality", async ({ page }) => { test("header navigation functionality", async ({ page }) => {
+1 -1
View File
@@ -123,7 +123,7 @@ describe("Page", () => {
const learnMore = screen.getAllByRole("link", { const learnMore = screen.getAllByRole("link", {
name: "Explore the patterns", name: "Explore the patterns",
}); });
expect(learnMore[0]).toHaveAttribute("href", "/learn"); expect(learnMore[0]).toHaveAttribute("href", "/templates");
}); });
test("renders ask organizer section with correct data", () => { test("renders ask organizer section with correct data", () => {
+9 -2
View File
@@ -12,11 +12,18 @@ function decl(name: string): string {
describe("Figma tokens", () => { describe("Figma tokens", () => {
it("points theme colors, radius, and focus at Figma", () => { it("points theme colors, radius, and focus at Figma", () => {
expect(decl("--radius-500")).toBe("24px"); expect(decl("--radius-500")).toBe("24px");
expect(decl("--color-surface-default-brand-royal")).toContain("royal-blue600"); expect(decl("--color-surface-default-brand-royal")).toContain("royal-blue700");
expect(decl("--color-surface-default-brand-kiwi")).toContain("kiwi800"); expect(decl("--color-surface-default-brand-kiwi")).toContain("kiwi800");
expect(decl("--color-content-default-brand-secondary")).toContain("yellow200"); expect(decl("--color-content-default-brand-secondary")).toContain("yellow200");
expect(decl("--color-content-invert-brand-secondary")).toContain("yellow800"); expect(decl("--color-content-invert-brand-secondary")).toContain("yellow800");
expect(decl("--color-content-invert-brand-royal")).toContain("royal-blue400"); expect(decl("--color-content-invert-brand-royal")).toContain("royal-blue700");
expect(decl("--color-content-invert-brand-navy")).toContain("royal-blue1000");
expect(decl("--color-content-invert-quaternary")).toContain("gray-500");
expect(decl("--color-content-invert-negative-primary")).toContain("red600");
expect(decl("--color-gray-150")).toBe("#d2d2d2");
expect(decl("--color-gray-500")).toBe("#717171");
expect(decl("--color-royal-blue-royal-blue700")).toBe("#233bb6");
expect(decl("--color-red-red600")).toBe("#b62e1a");
expect(decl("--color-content-default-utility-negative")).toContain("red300"); expect(decl("--color-content-default-utility-negative")).toContain("red300");
expect(decl("--color-content-default-negative-primary")).toContain( expect(decl("--color-content-default-negative-primary")).toContain(
"utility-negative", "utility-negative",