Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/components/MarketCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -80,7 +81,7 @@ export function MarketCard({

return (
<Card
className={`border-white/10 bg-[#201F3780] p-4 backdrop-blur-sm transition-all duration-300 hover:bg-[#201F3780]/80 ${
className={`market-card border-white/10 bg-[#201F3780] p-4 backdrop-blur-sm transition-all duration-300 hover:bg-[#201F3780]/80 ${
reducedMotion ? "" : "animate-slide-up"
} ${className ?? ""}`}
style={{
Expand Down
81 changes: 81 additions & 0 deletions app/components/__tests__/MarketCard.contrast.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from "react";
import { render, act } from "@testing-library/react";
import { useFollowsStore } from "@/app/state/follows";
import { useUserLimitsStore } from "@/app/state/userLimits";
import type { Market } from "@/content/markets.sample";

// MarketCard.tsx uses Tooltip and SaveForLater as globals (not imported).
// Define them in the test scope so the module can require them.
(globalThis as any).Tooltip = ({ children }: { children: React.ReactNode }) => <>{children}</>;
(globalThis as any).SaveForLater = () => <button>Save</button>;

// ── stubs for Card ───────────────────────────────────────────────────────────
jest.mock("@/components/ui/card", () => ({
Card: ({
children,
className,
style,
}: React.PropsWithChildren<{
className?: string;
style?: React.CSSProperties;
}>) => (
<div className={className} style={style}>
{children}
</div>
),
}));

// ── 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(<MarketCard market={SAMPLE_MARKET} />);
const card = document.querySelector(".market-card");
expect(card).toBeInTheDocument();
});

it("applies market-card class alongside other base classes", () => {
render(<MarketCard market={SAMPLE_MARKET} />);
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(<MarketCard market={SAMPLE_MARKET} />);
}).not.toThrow();
});
});
65 changes: 65 additions & 0 deletions app/styles/themes/market-card-contrast.css
Original file line number Diff line number Diff line change
@@ -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;
}
}