Spaces:
Sleeping
Sleeping
UI v3: fintech-direction design pass (5 grafts), waterfall historical-mode fix, explain-strip placement + shared hook, palette contrast fix
20abf65 verified | // Display formatting ONLY β no business arithmetic happens in the UI. | |
| // Every number shown comes from the engine via the API. | |
| export const fmtMillions = (v) => | |
| v == null || Number.isNaN(Number(v)) ? 'β' : `$${Number(v).toFixed(1)}m`; | |
| export const fmtPct = (v, dp = 2) => | |
| v == null || Number.isNaN(Number(v)) ? 'β' : `${(Number(v) * 100).toFixed(dp)}%`; | |
| export const fmtRatio = (v) => | |
| v == null || Number.isNaN(Number(v)) ? 'β' : `${Number(v).toFixed(3)}Γ`; | |
| export const fmtSignedM = (v) => | |
| v == null || Number.isNaN(Number(v)) | |
| ? 'β' | |
| : `${Number(v) >= 0 ? '+' : 'β'}$${Math.abs(Number(v)).toFixed(1)}m`; | |
| export const fmtTime = (ms) => { | |
| const d = new Date(ms); | |
| const p = (n) => String(n).padStart(2, '0'); | |
| return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`; | |
| }; | |
| // A "*_pct" field is already 0-100 scale (per the contract's convention) β | |
| // never re-multiply it. | |
| export const fmtPctScale = (v, dp = 1) => | |
| v == null || Number.isNaN(Number(v)) ? 'β' : `${Number(v).toFixed(dp)}%`; | |
| export const fmtNum = (v, dp = 0) => | |
| v == null || Number.isNaN(Number(v)) | |
| ? 'β' | |
| : Number(v).toLocaleString(undefined, { | |
| minimumFractionDigits: dp, | |
| maximumFractionDigits: dp, | |
| }); | |
| export const fmtHazard = (v) => | |
| v == null || Number.isNaN(Number(v)) ? 'β' : Number(v).toFixed(4); | |
| export const fmtSigned = (v, dp = 1) => | |
| v == null || Number.isNaN(Number(v)) | |
| ? 'β' | |
| : `${Number(v) >= 0 ? '+' : 'β'}${Math.abs(Number(v)).toFixed(dp)}`; | |
| // Exhibit-footer "run <date>" stamp (FINAL_SPEC Β§5.2) β the client's own | |
| // clock; the app has no server-side "as-of build" timestamp endpoint. | |
| export const runDate = () => new Date().toISOString().slice(0, 10); | |