From 6cc882d609609e013e9e61909a3e68a07c626638 Mon Sep 17 00:00:00 2001 From: waterWang Date: Sun, 9 Aug 2026 03:44:40 +0800 Subject: [PATCH] feat(market-card): add prefers-contrast: more support with explicit border+text overrides (Closes #772) --- app/components/MarketCard.tsx | 3 +- .../__tests__/MarketCard.contrast.test.tsx | 81 +++++++++++++++++++ app/styles/themes/market-card-contrast.css | 65 +++++++++++++++ 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 app/components/__tests__/MarketCard.contrast.test.tsx create mode 100644 app/styles/themes/market-card-contrast.css diff --git a/app/components/MarketCard.tsx b/app/components/MarketCard.tsx index bea12bb3..de0507ff 100644 --- a/app/components/MarketCard.tsx +++ b/app/components/MarketCard.tsx @@ -13,6 +13,7 @@ import { useUserLimitsStore } from "@/app/state/userLimits" import Sparkline from "@/components/Sparkline" import { HeatStrip } from "@/app/components/HeatStrip" import { BookmarkButton } from "@/app/components/BookmarkButton" +import "../styles/themes/market-card-contrast.css" // --------------------------------------------------------------------------- // Icon / colour mapping (internal – consumers need only pass a Market) @@ -80,7 +81,7 @@ export function MarketCard({ return ( <>{children}; +(globalThis as any).SaveForLater = () => ; + +// ── stubs for Card ─────────────────────────────────────────────────────────── +jest.mock("@/components/ui/card", () => ({ + Card: ({ + children, + className, + style, + }: React.PropsWithChildren<{ + className?: string; + style?: React.CSSProperties; + }>) => ( +
+ {children} +
+ ), +})); + +// ── fixture ────────────────────────────────────────────────────────────────── +const SAMPLE_MARKET: Market = { + id: "test-market", + title: "Test Market", + description: "A test market description", + icon: "TrendingUp", + iconColor: "blue", + yesOdds: 65, + noOdds: 35, + poolAmount: 5000, + endsIn: "5 days", + sparklineData: [40, 50, 60, 55, 65, 70, 65], + activity24h: [ + 10, 8, 4, 2, 1, 3, 8, 20, 40, 60, 75, 85, 90, 88, 82, 78, 68, 55, 45, 35, + 28, 20, 14, 10, + ], + status: "active", +}; + +function resetStores() { + act(() => { + useFollowsStore.setState({ followedIds: new Set() }); + useUserLimitsStore.setState({ + remainingDailyAllowance: { "test-market": 200 }, + }); + }); + window.localStorage.clear(); +} + +const { MarketCard } = require("../MarketCard"); + +describe("MarketCard prefers-contrast: more", () => { + beforeEach(resetStores); + + it("applies the market-card CSS class for high-contrast targeting", () => { + render(); + const card = document.querySelector(".market-card"); + expect(card).toBeInTheDocument(); + }); + + it("applies market-card class alongside other base classes", () => { + render(); + const card = document.querySelector(".market-card"); + expect(card).toBeInTheDocument(); + expect(card?.className).toContain("border-white/10"); + expect(card?.className).toContain("bg-[#201F3780]"); + }); + + it("renders without errors", () => { + expect(() => { + render(); + }).not.toThrow(); + }); +}); \ No newline at end of file diff --git a/app/styles/themes/market-card-contrast.css b/app/styles/themes/market-card-contrast.css new file mode 100644 index 00000000..6be697ce --- /dev/null +++ b/app/styles/themes/market-card-contrast.css @@ -0,0 +1,65 @@ +/* ========================================================================== + MarketCard prefers-contrast: more overrides + ========================================================================== + Activated automatically when the user's OS / browser is set to + "High Contrast" mode (prefers-contrast: more). + Replaces semi-transparent borders and muted text with solid, + high-contrast alternatives so the card remains readable at + WCAG AAA (≥ 7:1) for normal text. + ========================================================================== */ + +@media (prefers-contrast: more) { + /* ── Card border ──────────────────────────────────────────────────────── */ + .market-card { + border-color: rgba(255, 255, 255, 0.8) !important; + border-width: 2px !important; + } + + /* ── Text contrast ────────────────────────────────────────────────────── */ + .market-card h3 { + color: #ffffff !important; + } + + .market-card p, + .market-card .text-white\/70 { + color: #e5e7eb !important; /* gray-200 — ≈ 15:1 on #1e1b4b */ + } + + .market-card .text-white\/85 { + color: #f3f4f6 !important; /* gray-100 — ≈ 17:1 */ + } + + .market-card .text-white\/60 { + color: #d1d5db !important; /* gray-300 — ≈ 12:1 */ + } + + /* ── Odds values ──────────────────────────────────────────────────────── */ + .market-card .text-green-400 { + color: #4ade80 !important; /* green-400 — explicit v. bg */ + } + + .market-card .text-red-400 { + color: #f87171 !important; /* red-400 — explicit v. bg */ + } + + /* ── Probability bar ───────────────────────────────────────────────────── */ + .market-card .bg-white\/10 { + background-color: rgba(255, 255, 255, 0.3) !important; + } + + /* ── Following indicator ───────────────────────────────────────────────── */ + .market-card [data-testid="following-indicator"] { + border: 1px solid #c084fc !important; /* purple-400 */ + } + + /* ── Betting-limit nudge ───────────────────────────────────────────────── */ + .market-card [data-testid="betting-limit-nudge"] { + border-color: rgba(255, 255, 255, 0.5) !important; + background-color: rgba(255, 255, 255, 0.1) !important; + } + + /* ── Disable backdrop blur (can reduce contrast) ───────────────────────── */ + .market-card { + backdrop-filter: none !important; + } +} \ No newline at end of file