fix(scenarios): band picker order, letter contrast, quieter excluded chips

Design feedback:
- Target-band picker reads A→G left-to-right (reading order; the
  gallery/detail meters stay G→A as they mirror the building passport's
  efficiency gauge, which fills low→high).
- Unselected picker chips are now light band-colour tints with one
  consistent dark ink — no ink has legal contrast across all seven band
  colours at full saturation, so the old full-colour+opacity fade made
  letter colours look inconsistent and washed out. Only the selected
  chip goes full colour, with luminance-correct ink (shared bandInk()).
- Excluded measure chips recede (gray, struck through) instead of
  filling dark navy — "Exclude all" no longer paints a wall of blue;
  the measures still in play are the visually dominant ones. State is
  never colour-only: ✓/✕ icon + strikethrough carry it too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-06 08:56:52 +00:00
parent fdae966461
commit 34dcb0a54f
2 changed files with 35 additions and 16 deletions

View file

@ -11,7 +11,7 @@ import {
type MeasureKey,
} from "@/app/db/schema/recommendations";
import { EPC_BANDS, findExactDuplicate, SCENARIO_GOALS } from "@/lib/scenarios/model";
import { BandChip, EPC_SOFT, GOAL_DESCRIPTIONS, GOAL_SHORT_LABELS, GoalIcon, formatMoney } from "../ui";
import { BandChip, EPC_SOFT, GOAL_DESCRIPTIONS, GOAL_SHORT_LABELS, GoalIcon, bandInk, bandTint, formatMoney } from "../ui";
interface ExistingScenario {
id: string;
@ -293,20 +293,28 @@ export function NewScenarioJourney({
<div className="mb-5 pb-5">
<label className={fieldLabel}>Target band</label>
<div className="flex max-w-[480px] gap-1.5">
{[...EPC_BANDS].reverse().map((b) => (
{EPC_BANDS.map((b) => (
<button
key={b}
aria-pressed={band === b}
className="relative h-[52px] rounded-xl border-2 font-manrope text-lg font-extrabold transition-all"
style={{
background: EPC_SOFT[b],
color: ["C", "D", "E"].includes(b) ? "#333a1d" : "#fff",
opacity: band === b ? 1 : 0.38,
flexGrow: band === b ? 1.7 : 1,
transform: band === b ? "translateY(-3px)" : undefined,
borderColor: band === b ? "#14163d" : "transparent",
boxShadow: "inset 0 -12px 16px rgba(0,0,0,.10)",
}}
style={
band === b
? {
background: EPC_SOFT[b],
color: bandInk(b),
flexGrow: 1.7,
transform: "translateY(-3px)",
borderColor: "#14163d",
boxShadow: "inset 0 -12px 16px rgba(0,0,0,.10)",
}
: {
background: bandTint(b),
color: "#3f4757",
flexGrow: 1,
borderColor: "transparent",
}
}
onClick={() => setBand(b)}
>
{b}
@ -395,10 +403,9 @@ export function NewScenarioJourney({
aria-pressed={off}
className={`inline-flex items-center gap-2 rounded-full border px-3.5 py-1.5 text-[13px] transition ${
off
? "border-transparent text-indigo-200 line-through"
: "border-gray-200 bg-white hover:border-gray-400"
? "border-gray-200 bg-gray-100 text-gray-400 line-through hover:border-gray-300"
: "border-gray-200 bg-white text-brandblue hover:border-gray-400"
}`}
style={off ? { background: "linear-gradient(135deg,#20234f,#14163d)" } : undefined}
onClick={() =>
setExcluded((prev) => {
const nextSet = new Set(prev);
@ -408,7 +415,7 @@ export function NewScenarioJourney({
})
}
>
<span className={off ? "text-indigo-300" : "font-bold text-brandmidblue"}>
<span className={off ? "text-gray-400" : "font-bold text-brandmidblue"}>
{off ? "✕" : "✓"}
</span>
{measuresDisplayLabels[k]}

View file

@ -14,6 +14,18 @@ export const EPC_SOFT: Record<string, string> = {
};
const DARK_TEXT_BANDS = new Set(["C", "D", "E"]);
/** Letter ink with adequate contrast on the band's full colour (dark on the light mid-bands, white on the saturated ends). */
export function bandInk(band: string): string {
return DARK_TEXT_BANDS.has(band) ? "#333a1d" : "#fff";
}
/** The band colour as a light tint — used for unselected picker chips so every letter can share one dark ink. */
export function bandTint(band: string, alpha = 0.22): string {
const hex = (EPC_SOFT[band] ?? "#9ca3af").slice(1);
const [r, g, b] = [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16));
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
export const GOAL_DESCRIPTIONS: Record<string, string> = {
[PORTFOLIO_GOALS.EPC]: "Bring every property up to a target band.",
[PORTFOLIO_GOALS.CO2]: "Cut carbon as far as possible, within any budget you set.",
@ -49,7 +61,7 @@ export function BandChip({ band, size = 34 }: { band: string; size?: number }) {
height: size,
fontSize: size / 2,
background: EPC_SOFT[band],
color: DARK_TEXT_BANDS.has(band) ? "#333a1d" : "#fff",
color: bandInk(band),
boxShadow: "inset 0 -10px 14px rgba(0,0,0,.12)",
}}
>