diff --git a/pages/auth-files/__tests__/AuthFilesPage.files-table.test.tsx b/pages/auth-files/__tests__/AuthFilesPage.files-table.test.tsx index 13685863..45a09511 100644 --- a/pages/auth-files/__tests__/AuthFilesPage.files-table.test.tsx +++ b/pages/auth-files/__tests__/AuthFilesPage.files-table.test.tsx @@ -5045,7 +5045,7 @@ describe("AuthFilesPage files table", () => { expect(await screen.findByText("Code: 5h")).toBeInTheDocument(); expect(screen.getByText("Code: Weekly")).toBeInTheDocument(); expect(screen.queryByText("Review: Weekly")).not.toBeInTheDocument(); - expect(screen.getByText("0%")).toHaveClass("text-rose-700"); + expect(screen.getByText("0%")).toHaveClass("text-rose-900"); }); test("cards view shows codex team subscription quota instead of empty stable placeholders", async () => { @@ -5169,7 +5169,7 @@ describe("AuthFilesPage files table", () => { expect(metrics[3]).toHaveAttribute("data-layout", "wide"); const previewZero = within(row as HTMLElement).getByText("0%"); - expect(previewZero).toHaveClass("text-rose-700"); + expect(previewZero).toHaveClass("text-rose-900"); // Every chip carries its own countdown inline, so hovering opens nothing. for (const metric of metrics) { diff --git a/pages/auth-files/components/AuthFilesFilesTab.tsx b/pages/auth-files/components/AuthFilesFilesTab.tsx index 6d7f8a25..68c8ab6b 100644 --- a/pages/auth-files/components/AuthFilesFilesTab.tsx +++ b/pages/auth-files/components/AuthFilesFilesTab.tsx @@ -1727,7 +1727,7 @@ export function AuthFilesFilesTab({ successRate === null ? "text-slate-500 dark:text-white/45" : successRate >= 90 - ? "text-slate-700 dark:text-white/70" + ? "text-emerald-700 dark:text-emerald-200" : successRate >= 50 ? "text-amber-700 dark:text-amber-200" : "text-rose-700 dark:text-rose-200"; diff --git a/pages/auth-files/components/QuotaMetricChips.tsx b/pages/auth-files/components/QuotaMetricChips.tsx index b5707441..29ef4216 100644 --- a/pages/auth-files/components/QuotaMetricChips.tsx +++ b/pages/auth-files/components/QuotaMetricChips.tsx @@ -4,6 +4,11 @@ import { clampPercent, type QuotaItem } from "@features/quota-preview/quota-help export type QuotaVisualTone = { normalized: number | null; + /** + * Bar fill: a light tint plus a 2px rule down its right edge. The rule is + * what marks the percentage, which frees the tint to stay light enough to + * read text over. + */ fillClass: string; percentClass: string; fillHex: string; @@ -11,6 +16,12 @@ export type QuotaVisualTone = { chipClass: string; /** Muted label color that stays legible on the chip surface. */ chipLabelClass: string; + /** Bar track: border, plus the background showing left of the fill. */ + barTrackClass: string; + /** Bar label, which sits over the fill and has to read against it. */ + barLabelClass: string; + /** Bar countdown: quieter than the label, still legible over the fill. */ + barMetaClass: string; }; export const resolveQuotaVisualTone = (percent: number | null | undefined): QuotaVisualTone => { @@ -19,54 +30,68 @@ export const resolveQuotaVisualTone = (percent: number | null | undefined): Quot if (normalized === null) { return { normalized, - fillClass: "bg-slate-300/50 dark:bg-white/10", + fillClass: + "border-r-2 border-slate-300 bg-slate-100 dark:border-white/20 dark:bg-white/[0.07]", percentClass: "text-slate-900 dark:text-white", fillHex: "#cbd5e1", chipClass: "border-slate-900/8 bg-slate-50 dark:border-white/10 dark:bg-white/[0.06]", chipLabelClass: "text-slate-600 dark:text-white/70", + barTrackClass: "border-slate-200 bg-white dark:border-white/10 dark:bg-white/[0.03]", + barLabelClass: "text-slate-700 dark:text-white/80", + barMetaClass: "text-slate-500 dark:text-white/50", }; } - // A healthy quota is the common case, so it gets no colour beyond the ring: - // tinting the chip surface, its label and its percent all at once made every - // card on the page read as one flat green field, and a green that is always - // on cannot signal anything. Amber and rose below keep their full treatment — - // they are the states worth spotting from across the grid. + // The bar's fill is the row's own background, so at 100% it covers the entire + // line — and repeated down a grid of cards, a saturated fill becomes a wall of + // colour that is tiring to look at and, being always on, signals nothing. + // + // So the tint stays light and a 2px rule at the fill's right edge carries the + // percentage instead. The edge marks the value more precisely than a block + // boundary does, and the label keeps its contrast because it is no longer + // sitting on a saturated ground. if (normalized >= 60) { return { normalized, - // The card view spends this on renderQuotaBarNode's fill, which is the - // row's own background rather than a slim track — at 100% it covers the - // whole line, so an emerald fill turned every healthy row into a green - // band. fillHex below stays green: it drives the 14px ring, where the - // colour costs nothing. - fillClass: "bg-slate-400 dark:bg-white/30", - percentClass: "text-slate-700 dark:text-white/80", + fillClass: + "border-r-2 border-emerald-400 bg-emerald-100 dark:border-emerald-400/70 dark:bg-emerald-500/20", + percentClass: "text-emerald-900 dark:text-emerald-100", fillHex: "#10b981", - chipClass: "border-slate-900/8 bg-slate-50 dark:border-white/10 dark:bg-white/[0.06]", - chipLabelClass: "text-slate-600 dark:text-white/70", + chipClass: + "border-emerald-200/70 bg-emerald-50/70 dark:border-emerald-500/20 dark:bg-emerald-500/[0.08]", + chipLabelClass: "text-emerald-900 dark:text-emerald-100/80", + barTrackClass: "border-emerald-200 bg-white dark:border-emerald-500/20 dark:bg-white/[0.03]", + barLabelClass: "text-emerald-900 dark:text-emerald-50", + barMetaClass: "text-emerald-700 dark:text-emerald-200/70", }; } if (normalized >= 20) { return { normalized, - fillClass: "bg-amber-500", - percentClass: "text-amber-700 dark:text-amber-200", + fillClass: + "border-r-2 border-amber-400 bg-amber-100 dark:border-amber-400/70 dark:bg-amber-500/20", + percentClass: "text-amber-900 dark:text-amber-100", fillHex: "#f59e0b", chipClass: "border-amber-200/70 bg-amber-50/70 dark:border-amber-500/20 dark:bg-amber-500/[0.08]", - chipLabelClass: "text-amber-900/70 dark:text-amber-100/70", + chipLabelClass: "text-amber-900 dark:text-amber-100/80", + barTrackClass: "border-amber-200 bg-white dark:border-amber-500/20 dark:bg-white/[0.03]", + barLabelClass: "text-amber-900 dark:text-amber-50", + barMetaClass: "text-amber-700 dark:text-amber-200/70", }; } return { normalized, - fillClass: "bg-rose-500", - percentClass: "text-rose-700 dark:text-rose-200", + fillClass: "border-r-2 border-rose-400 bg-rose-100 dark:border-rose-400/70 dark:bg-rose-500/20", + percentClass: "text-rose-900 dark:text-rose-100", fillHex: "#f43f5e", chipClass: "border-rose-200/70 bg-rose-50/70 dark:border-rose-500/20 dark:bg-rose-500/[0.08]", - chipLabelClass: "text-rose-900/70 dark:text-rose-100/70", + chipLabelClass: "text-rose-900 dark:text-rose-100/80", + barTrackClass: "border-rose-200 bg-white dark:border-rose-500/20 dark:bg-white/[0.03]", + barLabelClass: "text-rose-900 dark:text-rose-50", + barMetaClass: "text-rose-700 dark:text-rose-200/70", }; }; diff --git a/pages/auth-files/hooks/quotaBar.tsx b/pages/auth-files/hooks/quotaBar.tsx index 0db25fd1..50cf6c26 100644 --- a/pages/auth-files/hooks/quotaBar.tsx +++ b/pages/auth-files/hooks/quotaBar.tsx @@ -46,15 +46,16 @@ export const renderQuotaBarNode = ( const bar = (
+ {/* No opacity wrapper: the tone ships a tint already light enough to read + text over, and dimming it further was what made the fill edge — the + thing that actually encodes the percentage — impossible to locate. */}