Files
learnharness/components/confidence-rail.tsx
T
amosblomqvist 6da192bb22 answered quiz cards show the confidence rail — the slider's record form
The rail paint is extracted to components/confidence-rail.tsx (ramp,
tick stack, geometry laws) and shared by both surfaces so they cannot
drift: the dock's live slider paints the rail under its disc, the
answered card renders it disc-less — the missing handle itself says
'record, not control'. Same look otherwise: thin rail, stop color,
solid ticks, % read-out on the right, dock-mirrored placement (below
the options, above the thinking).

Renders only where a confidence exists: quiz answers with one sent;
never on 'I don't know' (none was sent), never on explain/ask, never
on pre-feature records (optional field, absent).
2026-10-02 01:21:12 +03:00

62 lines
2.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { CSSProperties } from "react";
import { cn } from "@/lib/utils";
import { CONFIDENCE_STOPS } from "@/src/core";
/**
* The confidence RAIL — the slider's paint, shared by two surfaces so they
* can never drift apart (owner, 2026-10-01):
*
* - the dock's live slider (question-dock) — a transparent range input
* rides on top of this paint, disc and all;
* - the ANSWERED card (owner round 6) — the same rail with NO disc,
* which itself communicates "this is a record, not a control".
*
* Geometry law: the rail spans the thumb-CENTER range (inset half a 16px
* disc per side) so the dock's 0/100 stops sit flush at the very ends.
* The static card has no thumb, but keeps the identical shape.
*
* Stack law: #141414 base (the darker main background), SOLID stop ticks
* as background layers UNDER the fill, and the fill itself TRANSPARENT
* past the value — so ticks show only on the empty side, never over the
* color. Ticks skip 0 and 100 (the rail's ends already say those).
*/
/** The six-step ramp (owner ruling): 0 red → 100 emerald, indexed by stop. */
export const CONFIDENCE_COLORS: readonly string[] = [
"#ef4444", // 0
"#f97316", // 30
"#eab308", // 50
"#84cc16", // 70
"#22c55e", // 90
"#10b981", // 100
];
/** The rail's paint for a stop value (0–100). */
export function confidenceRailStyle(value: number): CSSProperties {
const color = CONFIDENCE_COLORS[CONFIDENCE_STOPS.indexOf(value)] ?? CONFIDENCE_COLORS[2]!;
return {
backgroundColor: "#141414",
backgroundImage: [
`linear-gradient(90deg, ${color} 0%, ${color} ${value}%, transparent ${value}%, transparent 100%)`,
...[30, 50, 70, 90].map(
() => "linear-gradient(90deg, rgba(230,230,230,0.35), rgba(230,230,230,0.35))",
),
].join(", "),
backgroundSize: ["100% 100%", "1px 100%", "1px 100%", "1px 100%", "1px 100%"].join(", "),
backgroundPosition: ["0 0", ...[30, 50, 70, 90].map((p) => `${p}% 0`)].join(", "),
backgroundRepeat: "no-repeat",
};
}
/** The static rail — the slider's look, disc removed. */
export function ConfidenceRail({ value, className }: { value: number; className?: string }) {
return (
<div
aria-hidden
data-testid="confidence-rail"
className={cn("h-1.5 w-full rounded-full border border-border", className)}
style={confidenceRailStyle(value)}
/>
);
}