diff --git a/app/components/controls/Chip/Chip.view.tsx b/app/components/controls/Chip/Chip.view.tsx
index d41a91e..1223426 100644
--- a/app/components/controls/Chip/Chip.view.tsx
+++ b/app/components/controls/Chip/Chip.view.tsx
@@ -109,7 +109,7 @@ function ChipView({
focus-visible:ring-[var(--color-border-default-primary,#141414)]
focus-visible:ring-offset-2
focus-visible:ring-offset-transparent
- transition-[background,border-color,color,box-shadow,transform]
+ transition-[background,border-color,color,box-shadow]
duration-200
ease-in-out
`
@@ -118,7 +118,7 @@ function ChipView({
const stateClasses = isDisabled
? "cursor-not-allowed opacity-60"
- : "cursor-pointer hover:scale-[1.02]";
+ : "cursor-pointer";
const combinedClasses = [
baseClasses,
diff --git a/app/components/controls/Incrementer/Incrementer.view.tsx b/app/components/controls/Incrementer/Incrementer.view.tsx
index 6a57b62..88d8b08 100644
--- a/app/components/controls/Incrementer/Incrementer.view.tsx
+++ b/app/components/controls/Incrementer/Incrementer.view.tsx
@@ -4,7 +4,7 @@ import { memo } from "react";
import type { IncrementerViewProps } from "./Incrementer.types";
const STEP_BUTTON_CLASSES =
- "bg-[var(--color-surface-default-secondary,#141414)] text-[var(--color-content-default-primary,#fff)] inline-flex shrink-0 items-center justify-center overflow-clip rounded-[var(--measures-radius-full,9999px)] px-[var(--space-200,8px)] py-[var(--measures-spacing-150,6px)] transition-[background,color,transform] duration-200 ease-in-out hover:scale-[1.02] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary,#fff)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-surface-default-primary,#000)] disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:scale-100";
+ "bg-[var(--color-surface-default-secondary,#141414)] text-[var(--color-content-default-primary,#fff)] inline-flex shrink-0 items-center justify-center overflow-clip rounded-[var(--measures-radius-full,9999px)] px-[var(--space-200,8px)] py-[var(--measures-spacing-150,6px)] transition-[background,color,opacity] duration-200 ease-in-out hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary,#fff)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-surface-default-primary,#000)] disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:opacity-50";
function MinusIcon() {
return (
diff --git a/app/components/navigation/MenuItem/MenuItem.container.tsx b/app/components/navigation/MenuItem/MenuItem.container.tsx
index b340ee7..cdba605 100644
--- a/app/components/navigation/MenuItem/MenuItem.container.tsx
+++ b/app/components/navigation/MenuItem/MenuItem.container.tsx
@@ -90,7 +90,7 @@ const MenuItemContainer = memo
(
// Interactive styles
const interactiveStyles =
- "hover:scale-[1.02] active:scale-[0.98] focus:scale-[1.02] disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:active:scale-100";
+ "active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100";
// Disabled styles
const disabledStyles = disabled
diff --git a/app/components/sections/LogoWall/LogoWall.view.tsx b/app/components/sections/LogoWall/LogoWall.view.tsx
index 5da57d7..02c7571 100644
--- a/app/components/sections/LogoWall/LogoWall.view.tsx
+++ b/app/components/sections/LogoWall/LogoWall.view.tsx
@@ -23,16 +23,14 @@ function LogoWallView({
{displayLogos.map((logo, index) => (
` when not disabled.
+- **Interaction:** hover and focus change color/border (or opacity/shadow on cards) — they must not scale the control, because that enlarges the label. Reserve `active:scale-[0.98]` for press. Same rule for nav items (`MenuItem`) and cards (`Mini`, `Icon`, article thumbnails).
- **`buttons/InlineTextButton/`** — tertiary inline **``** (underline, inherited typography) for in-paragraph controls—**not** primary actions.
## Control conventions (Figma [“Control”](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=5944-58611) canvas)
diff --git a/docs/guides/ops-backend-deploy.md b/docs/guides/ops-backend-deploy.md
index 8e49236..5d01268 100644
--- a/docs/guides/ops-backend-deploy.md
+++ b/docs/guides/ops-backend-deploy.md
@@ -225,10 +225,13 @@ maintenance window — after the final Cloudron backup (§5 phase 3 step
([CR-101](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-communityrule-lamp-app))
for operator recovery if manual lookup from the export is ever needed.
-**User discoverability:** link to the archive repo (or a release
-download) from the new app — footer, help page, or a static
-`/legacy-archive` page — so users looking for pre-cutover rules can
-find it without knowing Gitea exists.
+**No app-side surface.** The archive lives in the repo only. It exists
+for posterity and operator lookup, not end-user self-service — there is
+no footer link, help-page entry, or `/legacy-archive` page in the new
+app, and none is required to close
+[CR-102](https://linear.app/community-rule/issue/CR-102). Operators
+answering a "where did my rule go?" question point at the Gitea repo
+directly.
## 7. Old vs new deltas
diff --git a/stories/sections/LogoWall.stories.js b/stories/sections/LogoWall.stories.js
index c4fd586..5691f45 100644
--- a/stories/sections/LogoWall.stories.js
+++ b/stories/sections/LogoWall.stories.js
@@ -20,10 +20,6 @@ export default {
## Animations & Transitions
- **Fade-in Effect**: Logos fade in from opacity 0 to 60% after component mounts (500ms transition)
-- **Hover Interactions**: Individual logos respond to hover with:
- - Opacity change from 60% to 100%
- - Scale transform (105% zoom)
- - 500ms smooth transitions for all effects
- **Loading States**: Progressive loading with fallback timer for reliable display
## Props
@@ -74,7 +70,7 @@ export const Default = {
docs: {
description: {
story:
- "Default LogoWall with all partner logos. Displays in a 3×2 grid on mobile, 2×3 grid on small screens, single row on medium screens, and larger sizes on large screens. Features smooth fade-in animations and hover interactions.",
+ "Default LogoWall with all partner logos. Displays in a 3×2 grid on mobile, 2×3 grid on small screens, single row on medium screens, and larger sizes on large screens. Features a fade-in on mount.",
},
},
},
diff --git a/tests/components/Button.test.tsx b/tests/components/Button.test.tsx
index 8036b43..f942d60 100644
--- a/tests/components/Button.test.tsx
+++ b/tests/components/Button.test.tsx
@@ -61,6 +61,9 @@ describe("Button (behavioral tests)", () => {
expect(button.className).toContain("duration-150");
expect(button.className).not.toContain("duration-500");
expect(button.className).toContain("active:scale-[0.98]");
+ expect(button.className).not.toContain("hover:scale-");
+ expect(button.className).not.toContain("focus-visible:scale-");
+ expect(button.className).not.toContain("hover:outline-");
});
it("renders as a link when href is provided", () => {
diff --git a/tests/components/Logo.test.tsx b/tests/components/Logo.test.tsx
index c11a8a8..29abcf4 100644
--- a/tests/components/Logo.test.tsx
+++ b/tests/components/Logo.test.tsx
@@ -36,6 +36,16 @@ describe("Logo (behavioral tests)", () => {
expect(logo).toHaveAttribute("aria-label", "CommunityRule Logo");
});
+ it("animates the mark on hover without scaling the wordmark", () => {
+ render( );
+ const logo = screen.getByRole("link", { name: /communityrule logo/i });
+ const wordmark = screen.getByText("CommunityRule");
+ const mark = logo.querySelector("img");
+ expect(logo).toHaveClass("group");
+ expect(wordmark.className).not.toContain("scale-");
+ expect(mark).toHaveClass("group-hover:scale-110");
+ });
+
it("renders logo icon", () => {
render( );
expect(screen.getByAltText("CommunityRule Logo")).toBeInTheDocument();
diff --git a/tests/unit/LogoWall.test.jsx b/tests/unit/LogoWall.test.jsx
index 904cdab..3c7c8da 100644
--- a/tests/unit/LogoWall.test.jsx
+++ b/tests/unit/LogoWall.test.jsx
@@ -92,13 +92,15 @@ describe("LogoWall Component", () => {
expect(screen.getByAltText("Food Not Bombs")).toBeInTheDocument();
});
- test("applies hover effects", () => {
+ test("does not apply hover effects on partner logos", () => {
render( );
- const logoContainers = document.querySelectorAll(
- '[class*="hover:opacity-100"]',
- );
- expect(logoContainers.length).toBeGreaterThan(0);
+ expect(
+ document.querySelectorAll('[class*="hover:opacity"]').length,
+ ).toBe(0);
+ expect(
+ document.querySelectorAll('[class*="hover:scale"]').length,
+ ).toBe(0);
});
test("renders with proper semantic structure", () => {
@@ -142,13 +144,4 @@ describe("LogoWall Component", () => {
expect(foodNotBombsLogo).toBeInTheDocument();
expect(startCoopLogo).toBeInTheDocument();
});
-
- test("applies scale effect on hover", () => {
- render( );
-
- const logos = screen.getAllByRole("img");
- logos.forEach((logo) => {
- expect(logo).toHaveClass("hover:scale-105");
- });
- });
});