style: enforce Vazirmatn-FD Persian digits across world economy, bonds and entire dashboard

This commit is contained in:
alireza 2026-08-26 14:36:31 +03:30
parent d5dffd0980
commit 984d44a18b
2 changed files with 32 additions and 25 deletions

View File

@ -79,7 +79,12 @@ import { LmeView } from "@/components/LmeView";
import { ImeView } from "@/components/ImeView";
import { Building2 } from "lucide-react";
const API = API_BASE; // dev: "" (same-origin via Vite proxy); prod: VITE_API_BASE
const API = API_BASE;
const faDigits = (s: string | number | null | undefined): string => {
if (s == null) return "—";
return String(s).replace(/[0-9]/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[+d]);
}; // dev: "" (same-origin via Vite proxy); prod: VITE_API_BASE
export const Route = createFileRoute("/dashboard")({
head: () => ({
@ -1365,8 +1370,7 @@ const STEEL_FLAGS: Record<string, string> = {
"Saudi Arabia": "🇸🇦",
};
// ASCII→Persian digits for any already-formatted string (keeps $ % T B . , # ▼ ▲)
const faDigits = (s: string) => s.replace(/[0-9]/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[+d]);
// faDigits declared globally at top
const usd = (n: number, frac = 2) =>
faDigits(n.toLocaleString("en-US", { minimumFractionDigits: frac, maximumFractionDigits: frac }));
@ -2747,8 +2751,8 @@ function WorldEconomyView() {
const fmtCell = (col: (typeof WE_COLS)[number], val: number | null) => {
if (val == null) return "—";
if (col.gdp) return (val / 1000).toLocaleString("fa-IR", { maximumFractionDigits: 2 }) + " T$";
return formatNumber(val) + (col.suffix || "");
if (col.gdp) return faDigits((val / 1000).toFixed(2) + " T$");
return faDigits(formatNumber(val) + (col.suffix || ""));
};
const us10y = bonds.find((b) => b.code === "US10Y");
@ -2851,12 +2855,12 @@ function WorldEconomyView() {
</div>
<div className="mt-2 flex items-baseline justify-between">
<span className="text-2xl font-bold font-mono tabular-nums text-foreground">
{us10y?.yield ? us10y.yield.toFixed(3) + "%" : "—"}
{us10y?.yield ? faDigits(us10y.yield.toFixed(3) + "%") : "—"}
</span>
{us10y && <DeltaPill pct={us10y.change} dir={us10y.change >= 0 ? "up" : "down"} />}
</div>
<div className="mt-1 text-[11px] text-muted-foreground font-mono">
تغییر: {us10y ? (us10y.change_abs >= 0 ? "+" : "") + (us10y.change_abs * 100).toFixed(1) + " bps" : "—"}
تغییر: {us10y ? faDigits((us10y.change_abs >= 0 ? "+" : "") + (us10y.change_abs * 100).toFixed(1) + " bps") : "—"}
</div>
</div>
@ -2867,12 +2871,12 @@ function WorldEconomyView() {
</div>
<div className="mt-2 flex items-baseline justify-between">
<span className="text-2xl font-bold font-mono tabular-nums text-foreground">
{de10y?.yield ? de10y.yield.toFixed(3) + "%" : "—"}
{de10y?.yield ? faDigits(de10y.yield.toFixed(3) + "%") : "—"}
</span>
{de10y && <DeltaPill pct={de10y.change} dir={de10y.change >= 0 ? "up" : "down"} />}
</div>
<div className="mt-1 text-[11px] text-muted-foreground font-mono">
تغییر: {de10y ? (de10y.change_abs >= 0 ? "+" : "") + (de10y.change_abs * 100).toFixed(1) + " bps" : "—"}
تغییر: {de10y ? faDigits((de10y.change_abs >= 0 ? "+" : "") + (de10y.change_abs * 100).toFixed(1) + " bps") : "—"}
</div>
</div>
@ -2883,12 +2887,12 @@ function WorldEconomyView() {
</div>
<div className="mt-2 flex items-baseline justify-between">
<span className="text-2xl font-bold font-mono tabular-nums text-foreground">
{gb10y?.yield ? gb10y.yield.toFixed(3) + "%" : "—"}
{gb10y?.yield ? faDigits(gb10y.yield.toFixed(3) + "%") : "—"}
</span>
{gb10y && <DeltaPill pct={gb10y.change} dir={gb10y.change >= 0 ? "up" : "down"} />}
</div>
<div className="mt-1 text-[11px] text-muted-foreground font-mono">
تغییر: {gb10y ? (gb10y.change_abs >= 0 ? "+" : "") + (gb10y.change_abs * 100).toFixed(1) + " bps" : "—"}
تغییر: {gb10y ? faDigits((gb10y.change_abs >= 0 ? "+" : "") + (gb10y.change_abs * 100).toFixed(1) + " bps") : "—"}
</div>
</div>
@ -2899,7 +2903,7 @@ function WorldEconomyView() {
</div>
<div className="mt-2 flex items-baseline justify-between">
<span className={cn("text-2xl font-bold font-mono tabular-nums", (spread10_2 ?? 0) >= 0 ? "text-emerald-700" : "text-red-600")}>
{spread10_2 != null ? (spread10_2 >= 0 ? "+" : "") + (spread10_2 * 100).toFixed(1) + " bps" : "—"}
{spread10_2 != null ? faDigits((spread10_2 >= 0 ? "+" : "") + (spread10_2 * 100).toFixed(1) + " bps") : "—"}
</span>
<span className="text-[11px] px-2 py-0.5 rounded-full font-mono font-medium bg-muted text-muted-foreground">
{(spread10_2 ?? 0) >= 0 ? "نرمال" : "وارونه (Inverted)"}
@ -2943,28 +2947,28 @@ function WorldEconomyView() {
</div>
</td>
<td className="p-3 text-center font-mono text-xs text-muted-foreground">
<span className="px-2 py-0.5 rounded bg-muted/80">{b.tenor}</span>
<span className="px-2 py-0.5 rounded bg-muted/80">{faDigits(b.tenor)}</span>
</td>
<td className="p-3 text-center font-mono font-bold text-base text-foreground tabular-nums">
{b.yield != null ? b.yield.toFixed(3) + "%" : "—"}
{b.yield != null ? faDigits(b.yield.toFixed(3) + "%") : "—"}
</td>
<td className="p-3 text-center font-mono text-xs tabular-nums">
<DeltaPill pct={b.change} dir={isUp ? "up" : "down"} />
</td>
<td className="p-3 text-center font-mono text-xs tabular-nums text-foreground">
{b.change_abs != null ? (b.change_abs >= 0 ? "+" : "") + (b.change_abs * 100).toFixed(1) : "—"}
{b.change_abs != null ? faDigits((b.change_abs >= 0 ? "+" : "") + (b.change_abs * 100).toFixed(1)) : "—"}
</td>
<td className="p-3 text-center font-mono text-xs text-muted-foreground tabular-nums">
{b.low != null && b.high != null ? `${b.low.toFixed(2)} - ${b.high.toFixed(2)}` : "—"}
{b.low != null && b.high != null ? faDigits(`${b.low.toFixed(2)} - ${b.high.toFixed(2)}`) : "—"}
</td>
<td className={cn("p-3 text-center font-mono text-xs tabular-nums", (b.perf_w ?? 0) >= 0 ? "text-emerald-700" : "text-red-600")}>
{b.perf_w != null ? (b.perf_w >= 0 ? "+" : "") + b.perf_w.toFixed(2) + "%" : "—"}
{b.perf_w != null ? faDigits((b.perf_w >= 0 ? "+" : "") + b.perf_w.toFixed(2) + "%") : "—"}
</td>
<td className={cn("p-3 text-center font-mono text-xs tabular-nums", (b.perf_m ?? 0) >= 0 ? "text-emerald-700" : "text-red-600")}>
{b.perf_m != null ? (b.perf_m >= 0 ? "+" : "") + b.perf_m.toFixed(2) + "%" : "—"}
{b.perf_m != null ? faDigits((b.perf_m >= 0 ? "+" : "") + b.perf_m.toFixed(2) + "%") : "—"}
</td>
<td className={cn("p-3 text-center font-mono text-xs tabular-nums", (b.perf_y ?? 0) >= 0 ? "text-emerald-700" : "text-red-600")}>
{b.perf_y != null ? (b.perf_y >= 0 ? "+" : "") + b.perf_y.toFixed(2) + "%" : "—"}
{b.perf_y != null ? faDigits((b.perf_y >= 0 ? "+" : "") + b.perf_y.toFixed(2) + "%") : "—"}
</td>
<td className="p-3 text-center w-28">
{b.sparkline && b.sparkline.length > 1 && (

View File

@ -1,3 +1,4 @@
@import "vazirmatn/misc/Farsi-Digits/Vazirmatn-FD-font-face.css";
@import "vazirmatn/Vazirmatn-font-face.css";
@import "tailwindcss" source(none);
@source "../src";
@ -257,8 +258,8 @@
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--font-sans: Vazirmatn, system-ui, sans-serif;
--font-mono: Vazirmatn, system-ui, sans-serif;
--font-sans: "Vazirmatn FD", Vazirmatn, system-ui, sans-serif;
--font-mono: "Vazirmatn FD", Vazirmatn, system-ui, sans-serif;
}
:root {
@ -299,7 +300,7 @@
@layer base {
* { border-color: var(--color-border); }
/* Unzoomable PWA: block double-tap zoom (pinch handled by the viewport meta) */
html, body { font-family: var(--font-sans); touch-action: manipulation; }
html, body { font-family: "Vazirmatn FD", Vazirmatn, system-ui, sans-serif; touch-action: manipulation; }
body {
background-color: var(--color-background);
background-image:
@ -312,12 +313,14 @@
color: var(--color-foreground);
direction: rtl;
}
/* Enforce Vazirmatn for all numbers, tabular-nums, and monospace elements */
/* Enforce Vazirmatn with Persian digits for all numbers, tabular-nums, and monospace elements */
.font-mono,
.tabular-nums,
span.font-mono,
div.font-mono {
font-family: Vazirmatn, system-ui, sans-serif !important;
div.font-mono,
td.tabular-nums,
span.tabular-nums {
font-family: "Vazirmatn FD", Vazirmatn, system-ui, sans-serif !important;
font-feature-settings: "tnum" 1;
font-variant-numeric: tabular-nums;
}