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
+275 -212

No files matched your search

+1
View File
@@ -5,6 +5,7 @@ import MonitorPageContent from "./MonitorPageContent";
export const metadata: Metadata = routeMetadata("/monitor", {
title: messages.metadata.monitor.title,
description: messages.metadata.monitor.description,
});
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", {
title: messages.metadata.login.title,
description: messages.metadata.login.description,
robots: NO_INDEX_ROBOTS,
});
+1
View File
@@ -5,6 +5,7 @@ import ProfilePageClient from "./ProfilePageClient";
export const metadata: Metadata = routeMetadata("/profile", {
title: messages.metadata.profile.title,
description: messages.metadata.profile.description,
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)]",
};
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> = {
xsmall: "text-xx-small-label",
small: "text-x-small-label",
@@ -122,21 +130,21 @@ const Button = memo<ButtonProps>(
const variantStyles: Record<string, string> = {
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":
"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:
"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":
"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:
"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":
"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:
"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":
"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
@@ -144,7 +152,7 @@ const Button = memo<ButtonProps>(
// and cards: color, opacity, or shadow on hover; scale only on press.
// State prop is informational for Figma alignment; CSS pseudo-classes own
// 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 sharedA11y = {
+4 -4
View File
@@ -21,7 +21,7 @@ function StatView({
return (
<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 items-center">
@@ -29,16 +29,16 @@ function StatView({
variant={shapeVariant}
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}
</p>
</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}
</p>
</div>
{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 ? (
<>
{asOfPrefix ? <span>{`${asOfPrefix} `}</span> : null}
@@ -33,10 +33,10 @@ const ContentContainerContainer = memo<ContentContainerProps>(
const onLight = tone === "onLight";
const titleColor = onLight
? "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
? "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 resolvedSlug =
@@ -56,7 +56,7 @@ const CheckboxContainer = memo<CheckboxProps>(
// Default state: tertiary border, with hover and focus states via CSS
// Hover changes border to brand primary color
// 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
@@ -64,7 +64,7 @@ const CheckboxContainer = memo<CheckboxProps>(
// Inverse: transparent background, white border
// Hover changes border to brand primary color
// 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;
@@ -75,8 +75,8 @@ const CheckboxContainer = memo<CheckboxProps>(
// Checkmark color per Figma
const checkGlyphColor = checked
? isStandard
? "var(--color-content-default-brand-primary, #fefcc9)" // Light yellow/cream for standard mode
: "var(--color-content-invert-primary, #000000)" // Black for inverse mode
? "var(--color-content-default-brand-primary)"
: "var(--color-content-invert-primary)"
: "transparent";
// Label color
+11 -11
View File
@@ -8,7 +8,7 @@ function chipSurfaceStyle(
palette: ChipViewProps["palette"],
size: ChipViewProps["size"],
): CSSProperties {
const borderWidth = size === "s" ? "1.25px" : "2px";
const borderWidth = size === "s" ? "1.25px" : "1.5px";
const reset: CSSProperties = {
appearance: "none",
WebkitAppearance: "none",
@@ -64,14 +64,14 @@ function chipSurfaceStyle(
}
if (state === "selected") {
return paint(
"var(--color-surface-invert-brand-primary, #fefcc9)",
"var(--color-content-invert-primary, #000)",
"var(--color-border-default-brand-primary, #fdfaa8)",
"var(--color-surface-invert-brand-primary)",
"var(--color-content-invert-primary)",
"var(--color-border-default-brand-primary)",
);
}
return paint(
"transparent",
"var(--color-content-default-brand-primary, #fefcc9)",
"var(--color-content-default-brand-primary)",
"var(--color-border-default-control)",
);
}
@@ -185,7 +185,7 @@ function ChipView({
{onCheck && (
<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}
disabled={!inputValue || !inputValue.trim()}
onClick={(event) => {
@@ -202,7 +202,7 @@ function ChipView({
viewBox="0 0 12 12"
fill="none"
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
d="M10 3L4.5 8.5L2 6"
@@ -224,7 +224,7 @@ function ChipView({
onChange={(e) => onInputChange?.(e.target.value)}
onKeyDown={onInputKeyDown}
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={{
fontSize: isSmall
? "var(--sizing-300)"
@@ -240,7 +240,7 @@ function ChipView({
{onClose && (
<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}
onClick={(event) => {
event.stopPropagation();
@@ -253,7 +253,7 @@ function ChipView({
viewBox="0 0 12 12"
fill="none"
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
d="M9 3L3 9M3 3L9 9"
@@ -309,7 +309,7 @@ function ChipView({
{onRemove && !isDisabled && (
<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}`}
onClick={(event) => {
event.stopPropagation();
@@ -59,7 +59,7 @@ function MultiSelectView({
{!formHeader || !label ? (
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}
</p>
) : null
@@ -133,7 +133,7 @@ function MultiSelectView({
className={
!addButtonText
? // 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)
`${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
@@ -151,8 +151,8 @@ function MultiSelectView({
xmlns="http://www.w3.org/2000/svg"
className={`shrink-0 ${
!addButtonText && isInverse
? "text-[var(--color-content-invert-primary,black)]"
: "text-[var(--color-content-default-brand-primary,#fefcc9)]"
? "text-[var(--color-content-invert-primary)]"
: "text-[var(--color-content-default-brand-primary)]"
}`}
>
<path
@@ -167,8 +167,8 @@ function MultiSelectView({
<span
className={`font-medium ${isSmall ? "text-[length:var(--sizing-300)] leading-[14px]" : "text-medium-label"} ${
isInverse
? "text-[color:var(--color-content-invert-primary,black)]"
: "text-[color:var(--color-content-default-primary,white)]"
? "text-[color:var(--color-content-invert-primary)]"
: "text-[color:var(--color-content-default-primary)]"
}`}
>
{addButtonText}
@@ -53,15 +53,15 @@ const RadioButtonContainer = ({
// Focus shows shadow (double ring: 2px white inner, 4px dark outer)
// When checked, border is brand primary (but changes to invert tertiary on focus)
const defaultBorder = checked
? "border-[var(--color-border-default-brand-primary,#fdfaa8)]"
? "border-[var(--color-border-default-brand-primary)]"
: "border-[var(--color-border-default-control)]";
// When focused and checked, border should be invert tertiary (#2d2d2d) per Figma
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)]";
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
@@ -71,17 +71,17 @@ const RadioButtonContainer = ({
// Focus shows shadow (double ring: 2px dark inner, 4px white outer)
// When checked, border is brand primary (but changes to white on focus)
const defaultBorder = checked
? "border-[var(--color-border-default-brand-primary,#fdfaa8)]"
: "border-[var(--color-border-invert-primary,white)]";
? "border-[var(--color-border-default-brand-primary)]"
: "border-[var(--color-border-invert-primary)]";
// Hover border: inverse brand primary for both selected and unselected per Figma
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
const focusBorder = checked
? "focus:border-[var(--color-border-invert-primary,white)]"
: "focus:border-[var(--color-border-invert-primary,white)]";
? "focus:border-[var(--color-border-invert-primary)]"
: "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`;
}
@@ -39,9 +39,9 @@ export function RadioButtonView({
<div
className={`w-[16px] h-[16px] rounded-full transition-all duration-200 ${
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"
? "bg-[var(--color-content-default-primary,#000000)]"
? "bg-[var(--color-content-default-primary)]"
: "bg-transparent"
}`}
/>
@@ -52,8 +52,8 @@ export function RadioGroupView({
<span
className={`text-small-paragraph ${
mode === "inverse"
? "text-[var(--color-content-invert-secondary,#1f1f1f)]"
: "text-[var(--color-content-default-secondary,#b4b4b4)]"
? "text-[var(--color-content-invert-secondary)]"
: "text-[var(--color-content-default-secondary)]"
}`}
>
{option.subtext}
@@ -109,7 +109,7 @@ export function SelectInputView({
.replace(/\s+/g, " ");
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) {
@@ -135,7 +135,7 @@ export function SelectInputView({
// Text color based on filled state
const textColorClass = isFilled
? "text-[var(--color-content-default-primary)]"
: "text-[var(--color-content-default-tertiary,#b4b4b4)]";
: "text-[var(--color-content-default-tertiary)]";
// Chevron icon
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>
{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>
)}
@@ -167,7 +167,7 @@ export function SelectInputView({
)}
</div>
{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
</span>
)}
@@ -102,7 +102,7 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
if (disabled) {
return {
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)]",
inputWrapper: "relative",
focusRing: "",
@@ -171,7 +171,7 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
const inputClasses = `
w-full transition-all duration-200 ease-in-out
focus:outline-none focus:ring-0
placeholder:text-[var(--color-content-default-tertiary,#b4b4b4)]
placeholder:text-[var(--color-content-default-tertiary)]
${sizeStyles.input}
${stateStyles.input}
${className}
@@ -180,7 +180,7 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
// Text color for filled text (placeholder color is handled above)
const textColorClass = isFilled
? "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
const { handleChange, handleBlur } = useFormField<HTMLInputElement>(
@@ -20,22 +20,22 @@ function UploadView({
// Button styles based on active state
const buttonBgClass = isActive
? "bg-[var(--color-surface-invert-primary,white)]"
: "bg-[var(--color-surface-default-secondary,#141414)]";
? "bg-[var(--color-surface-invert-primary)]"
: "bg-[var(--color-surface-default-secondary)]";
const buttonTextColor = isActive
? "text-[color:var(--color-content-invert-primary,black)]"
: "text-[color:var(--color-content-invert-tertiary,#2d2d2d)]";
? "text-[color:var(--color-content-invert-primary)]"
: "text-[color:var(--color-content-invert-tertiary)]";
// Description text color based on active state
const descriptionTextColor = isActive
? "text-[color:var(--color-content-default-primary,white)]"
: "text-[color:var(--color-content-default-tertiary,#b4b4b4)]";
? "text-[color:var(--color-content-default-primary)]"
: "text-[color:var(--color-content-default-tertiary)]";
// Icon color based on active state
const iconColor = isActive
? "text-[color:var(--color-content-invert-primary,black)]"
: "text-[color:var(--color-content-invert-tertiary,#2d2d2d)]";
? "text-[color:var(--color-content-invert-primary)]"
: "text-[color:var(--color-content-invert-tertiary)]";
return (
<div
@@ -55,7 +55,7 @@ function UploadView({
{/* Upload container — max width per create-flow spec */}
<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"
? "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}>
{t("legal.termsOfService")}
</Link>
<Link href={t("legal.cookiesSettingsHref")} className={legalLinkClass}>
{t("legal.cookiesSettings")}
</Link>
</div>
</div>
</div>
@@ -63,22 +63,22 @@ const MenuItemContainer = memo<MenuItemProps>(
const defaultModeStyles: Record<"default" | "hover" | "selected", string> =
{
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:
"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:
"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)
const inverseModeStyles: Record<"default" | "hover" | "selected", string> =
{
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:
"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:
"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
@@ -33,6 +33,7 @@ const FeatureGridContainer = memo<FeatureGridProps>(
),
descriptionClassName: "text-[var(--color-content-invert-brand-lime)]",
iconSrc: getAssetPath(featureIconPath("arrow-left-right")),
href: "/how-it-works",
},
{
backgroundColor: "bg-[var(--color-surface-invert-brand-rust)]",
@@ -42,6 +43,7 @@ const FeatureGridContainer = memo<FeatureGridProps>(
),
descriptionClassName: "text-[var(--color-content-invert-brand-rust)]",
iconSrc: getAssetPath(featureIconPath("door-open")),
href: "/blog/integrating-new-members-without-dilution",
},
{
backgroundColor: "bg-[var(--color-surface-invert-brand-teal)]",
@@ -10,6 +10,7 @@ export interface Feature {
description: string;
descriptionClassName: string;
iconSrc: string;
href?: string;
}
export interface FeatureGridViewProps extends FeatureGridProps {
@@ -1,8 +1,35 @@
"use client";
import Link from "next/link";
import { useTranslation } from "../../../contexts/MessagesContext";
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). */
function FeatureGridView({
@@ -41,34 +68,25 @@ function FeatureGridView({
</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">
{features.map((feature) => (
<div
{features.map((feature) => {
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)]`;
if (feature.href) {
return (
<Link
key={feature.title}
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)]`}
href={feature.href}
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)]`}
>
<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"
/>
<FeatureTileBody feature={feature} />
</Link>
);
}
return (
<div key={feature.title} className={tileClassName}>
<FeatureTileBody feature={feature} />
</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>
</div>
))}
);
})}
</div>
</div>
</div>
@@ -75,7 +75,7 @@ function QuoteBlockView({
role="region"
>
<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 />
<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 messages from "../messages/en/index";
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 Icon from "./components/asset/icon";
import Button from "./components/buttons/Button";
import HeroDecor from "./components/sections/HeroBanner/HeroDecor";
export const metadata: Metadata = {
export const metadata: Metadata = routeMetadata("/404", {
title: messages.metadata.notFound.title,
description: messages.metadata.notFound.description,
robots: NO_INDEX_ROBOTS,
};
});
const NOT_FOUND_TEMPLATE_SLUGS = [
"consensus",
+83 -80
View File
@@ -202,14 +202,15 @@
/* Grays & alpha */
--color-gray-000: #ffffff;
--color-gray-050: #f6f6f6;
--color-gray-100: #e5e5e5;
--color-gray-200: #d2d2d2;
--color-gray-300: #b4b4b4;
--color-gray-400: #8f8f8f;
--color-gray-500: #666666;
--color-gray-600: #464646;
--color-gray-700: #2d2d2d;
--color-gray-800: #1f1f1f;
--color-gray-100: #e4e4e4;
--color-gray-150: #d2d2d2;
--color-gray-200: #c2c2c2;
--color-gray-300: #a4a4a4;
--color-gray-400: #8a8a8a;
--color-gray-500: #717171;
--color-gray-600: #5b5b5b;
--color-gray-700: #464646;
--color-gray-800: #303030;
--color-gray-900: #141414;
--color-gray-1000: #000000;
@@ -244,16 +245,16 @@
/* Lavender */
--color-lavender-lavender0: #f0ebff;
--color-lavender-lavender50: #e8defe;
--color-lavender-lavender100: #e0d1fd;
--color-lavender-lavender150: #d7c3f9;
--color-lavender-lavender200: #ceb5f6;
--color-lavender-lavender300: #bb97ea;
--color-lavender-lavender400: #a578d9;
--color-lavender-lavender500: #8d5ac4;
--color-lavender-lavender600: #743ea9;
--color-lavender-lavender700: #5a278c;
--color-lavender-lavender800: #41146c;
--color-lavender-lavender900: #2b074d;
--color-lavender-lavender100: #dccdf9;
--color-lavender-lavender150: #d1bdf3;
--color-lavender-lavender200: #c6adee;
--color-lavender-lavender300: #b490e2;
--color-lavender-lavender400: #a275d5;
--color-lavender-lavender500: #8f5cc6;
--color-lavender-lavender600: #7c47b2;
--color-lavender-lavender700: #67359b;
--color-lavender-lavender800: #50277e;
--color-lavender-lavender900: #38185c;
--color-lavender-lavender1000: #1a0033;
/* Lime */
@@ -274,61 +275,61 @@
/* Magenta */
--color-magenta-magenta0: #ffebf0;
--color-magenta-magenta50: #fedee7;
--color-magenta-magenta100: #fdd1dd;
--color-magenta-magenta150: #f9c3d2;
--color-magenta-magenta200: #f6b5c7;
--color-magenta-magenta300: #ea97ad;
--color-magenta-magenta400: #d97892;
--color-magenta-magenta500: #c45a76;
--color-magenta-magenta600: #a93e5b;
--color-magenta-magenta700: #8c2742;
--color-magenta-magenta800: #6c142b;
--color-magenta-magenta900: #4d071a;
--color-magenta-magenta100: #f8ccd8;
--color-magenta-magenta150: #f0bbca;
--color-magenta-magenta200: #eaaabc;
--color-magenta-magenta300: #dd8ba1;
--color-magenta-magenta400: #ce6f89;
--color-magenta-magenta500: #be5571;
--color-magenta-magenta600: #aa3f5b;
--color-magenta-magenta700: #922d47;
--color-magenta-magenta800: #761f33;
--color-magenta-magenta900: #571222;
--color-magenta-magenta1000: #33000e;
/* Red */
--color-red-red0: #ffebf1;
--color-red-red50: #fec9d6;
--color-red-red100: #fda8b7;
--color-red-red150: #f98c98;
--color-red-red200: #f66f78;
--color-red-red300: #ea4845;
--color-red-red400: #d93529;
--color-red-red500: #c42916;
--color-red-red600: #a9200b;
--color-red-red700: #8c1905;
--color-red-red800: #6c1301;
--color-red-red900: #4d0d00;
--color-red-red100: #ffb6c2;
--color-red-red150: #ffa4ac;
--color-red-red200: #ff9296;
--color-red-red300: #ff6a62;
--color-red-red400: #ef4c3d;
--color-red-red500: #d33a26;
--color-red-red600: #b62e1a;
--color-red-red700: #972613;
--color-red-red800: #771f0d;
--color-red-red900: #571707;
--color-red-red1000: #330800;
/* Royal Blue */
--color-royal-blue-royal-blue0: #ebecff;
--color-royal-blue-royal-blue50: #c9cdfe;
--color-royal-blue-royal-blue100: #a8adfd;
--color-royal-blue-royal-blue150: #8c91f9;
--color-royal-blue-royal-blue200: #6f74f6;
--color-royal-blue-royal-blue300: #4548ea;
--color-royal-blue-royal-blue400: #2b29d9;
--color-royal-blue-royal-blue500: #1c16c4;
--color-royal-blue-royal-blue600: #140ba9;
--color-royal-blue-royal-blue700: #0f058c;
--color-royal-blue-royal-blue800: #0c016c;
--color-royal-blue-royal-blue900: #0a004d;
--color-royal-blue-royal-blue100: #b9bfff;
--color-royal-blue-royal-blue150: #aab1ff;
--color-royal-blue-royal-blue200: #99a4ff;
--color-royal-blue-royal-blue300: #778cff;
--color-royal-blue-royal-blue400: #5674ff;
--color-royal-blue-royal-blue500: #3a5afe;
--color-royal-blue-royal-blue600: #2d4adb;
--color-royal-blue-royal-blue700: #233bb6;
--color-royal-blue-royal-blue800: #1d2c8f;
--color-royal-blue-royal-blue900: #171b65;
--color-royal-blue-royal-blue1000: #080033;
/* Rust */
--color-rust-rust0: #ffedeb;
--color-rust-rust50: #fed8d4;
--color-rust-rust100: #fdc4bd;
--color-rust-rust150: #f9b0a6;
--color-rust-rust200: #f69c8f;
--color-rust-rust300: #ea7a65;
--color-rust-rust400: #d96043;
--color-rust-rust500: #c44e29;
--color-rust-rust600: #a94417;
--color-rust-rust700: #8c3d0b;
--color-rust-rust800: #6c3604;
--color-rust-rust900: #4d2c01;
--color-rust-rust100: #ffc6bf;
--color-rust-rust150: #feb4aa;
--color-rust-rust200: #fda395;
--color-rust-rust300: #f4836e;
--color-rust-rust400: #e46a4c;
--color-rust-rust500: #cd5632;
--color-rust-rust600: #b04a1f;
--color-rust-rust700: #904111;
--color-rust-rust800: #703909;
--color-rust-rust900: #512f05;
--color-rust-rust1000: #332100;
/* Teal */
@@ -392,25 +393,25 @@
--color-border-invert-warning-primary: var(--color-yellow-yellow600);
--color-border-invert-negative-primary: var(--color-red-red300);
/* Link — nearest palette step for each off-ramp hex */
--color-link-primary: var(--color-gray-1000);
--color-link-primary-hover: var(--color-gray-500);
--color-link-primary-focus: var(--color-gray-500);
--color-link-primary-active: var(--color-gray-500);
--color-link-secondary: var(--color-gray-500);
--color-link-secondary-hover: var(--color-gray-700);
--color-link-secondary-focus: var(--color-gray-700);
--color-link-secondary-active: var(--color-gray-700);
--color-link-invert-primary: var(--color-gray-000);
--color-link-invert-primary-hover: var(--color-gray-300);
--color-link-invert-primary-focus: var(--color-gray-300);
--color-link-invert-primary-active: var(--color-gray-300);
--color-link-invert-secondary: var(--color-gray-300);
--color-link-invert-secondary-hover: var(--color-gray-050);
--color-link-invert-secondary-focus: var(--color-gray-050);
--color-link-invert-secondary-active: var(--color-gray-050);
--color-border-link-focus: var(--color-gray-1000);
--color-border-link-invert-focus: var(--color-gray-000);
/* Link — Navigation / Link binds content tokens */
--color-link-primary: var(--color-content-invert-primary);
--color-link-primary-hover: var(--color-content-invert-quaternary);
--color-link-primary-focus: var(--color-content-invert-quaternary);
--color-link-primary-active: var(--color-content-invert-quaternary);
--color-link-secondary: var(--color-content-invert-quaternary);
--color-link-secondary-hover: var(--color-content-invert-tertiary);
--color-link-secondary-focus: var(--color-content-invert-tertiary);
--color-link-secondary-active: var(--color-content-invert-tertiary);
--color-link-invert-primary: var(--color-content-default-primary);
--color-link-invert-primary-hover: var(--color-content-default-tertiary);
--color-link-invert-primary-focus: var(--color-content-default-tertiary);
--color-link-invert-primary-active: var(--color-content-default-tertiary);
--color-link-invert-secondary: var(--color-content-default-tertiary);
--color-link-invert-secondary-hover: var(--color-content-default-primary);
--color-link-invert-secondary-focus: var(--color-content-default-primary);
--color-link-invert-secondary-active: var(--color-content-default-primary);
--color-border-link-focus: var(--color-border-default-primary);
--color-border-link-invert-focus: var(--color-border-invert-primary);
/* Content */
--color-content-default-brand-accent: var(--color-opacity-700);
@@ -443,10 +444,12 @@
--color-content-invert-primary: var(--color-gray-1000);
--color-content-invert-secondary: var(--color-gray-800);
--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-secondary: var(--color-yellow-yellow800);
--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-rust: var(--color-rust-rust700);
--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-positive-primary: var(--color-kiwi-kiwi800);
--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 */
--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-primary: var(--color-yellow-yellow1000);
--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-teal: var(--color-teal-teal700);
--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",
"linkHref": "/learn",
"linkHref": "/templates",
"ariaLabel": "Feature tools and services",
"features": {
"decisionMaking": {
+1 -3
View File
@@ -31,9 +31,7 @@
"privacyPolicy": "Privacy Policy",
"privacyPolicyHref": "/privacy",
"termsOfService": "Terms of Service",
"termsOfServiceHref": "/terms",
"cookiesSettings": "Cookies Settings",
"cookiesSettingsHref": "/cookies"
"termsOfServiceHref": "/terms"
},
"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.",
+10 -6
View File
@@ -7,8 +7,8 @@
"templatesAlt": "CommunityRule governance templates"
},
"home": {
"title": "Build structure before you need it — CommunityRule",
"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.",
"title": "CommunityRule | Write down how your community works",
"description": "Write down how your community talks, who belongs, how it decides and what happens when it disagrees. Free for every community.",
"keywords": [
"community",
"governance",
@@ -47,16 +47,20 @@
"fallbackDescription": "A blog post from our community."
},
"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": {
"title": "Profile"
"title": "Profile",
"description": "Manage your CommunityRules, saved progress, and account."
},
"monitor": {
"title": "Monitor"
"title": "Monitor",
"description": "Real-time monitoring of Core Web Vitals and performance metrics for Community Rule 3.0"
},
"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": {
"title": "Use cases",
+16 -6
View File
@@ -47,22 +47,32 @@ describe("FeatureGrid (behavioral tests)", () => {
).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" />);
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.queryByRole("link", { name: "Choose how you decide" }),
screen.queryByRole("link", { name: /Choose how you decide/ }),
).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" />);
expect(
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", () => {
+3 -3
View File
@@ -110,7 +110,7 @@ describe("Footer (behavioral tests)", () => {
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 />);
expect(
screen.getAllByRole("link", { name: "Privacy Policy" })[0],
@@ -119,7 +119,7 @@ describe("Footer (behavioral tests)", () => {
screen.getAllByRole("link", { name: "Terms of Service" })[0],
).toHaveAttribute("href", "/terms");
expect(
screen.getAllByRole("link", { name: "Cookies Settings" })[0],
).toHaveAttribute("href", "/cookies");
screen.queryByRole("link", { name: "Cookies Settings" }),
).not.toBeInTheDocument();
});
});
+4 -4
View File
@@ -37,7 +37,7 @@ describe("Upload (behavioral tests)", () => {
render(<Upload label="Upload" />);
const button = screen.getByRole("button", { name: /upload/i });
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} />);
const button = screen.getByRole("button", { name: /upload/i });
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;
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;
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"),
).toBeVisible();
// Feature tiles are presentational — only the lockup "Explore the patterns" is a link
await expect(
featureSection.locator('a[href="#decision-making"]'),
).toHaveCount(0);
await expect(
featureSection.getByRole("link", { name: "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(
featureSection.getByRole("link", { name: "Explore the patterns" }),
).toHaveAttribute("href", "/learn");
).toHaveAttribute("href", "/templates");
});
test("header navigation functionality", async ({ page }) => {
+1 -1
View File
@@ -123,7 +123,7 @@ describe("Page", () => {
const learnMore = screen.getAllByRole("link", {
name: "Explore the patterns",
});
expect(learnMore[0]).toHaveAttribute("href", "/learn");
expect(learnMore[0]).toHaveAttribute("href", "/templates");
});
test("renders ask organizer section with correct data", () => {
+9 -2
View File
@@ -12,11 +12,18 @@ function decl(name: string): string {
describe("Figma tokens", () => {
it("points theme colors, radius, and focus at Figma", () => {
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-content-default-brand-secondary")).toContain("yellow200");
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-negative-primary")).toContain(
"utility-negative",