Match the 3 Nov ramps and leftover launch QA #95

Merged
an.di merged 1 commits from adilallo/fix/CR-267-open-qa into main 2026-11-05 22:46:04 +00:00
32 changed files with 276 additions and 213 deletions
Showing only changes of commit ca928e306b - Show all commits

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
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)]`}
>
<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}`}
{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}
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)]`}
>
{feature.description}
</p>
<FeatureTileBody feature={feature} />
</Link>
);
}
return (
<div key={feature.title} className={tileClassName}>
<FeatureTileBody feature={feature} />
</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",