524 lines
23 KiB
React
524 lines
23 KiB
React
import { useLang } from "../LanguageContext";
|
||
|
||
// Four product interfaces drawn at 720x460. Each moves through three states:
|
||
// 0 drawing (dashed blueprint + dimensions), 1 object (filled), 2 running (live + measured callout).
|
||
const Bar = ({ w, h = 6, c = "rgba(18,20,19,.14)", className = "", style }) => (
|
||
<div className={`rounded-full ${className}`} style={{ width: w, height: h, background: c, ...style }} />
|
||
);
|
||
|
||
const Win = ({ children, title, crumb, right }) => (
|
||
<div className="bp ui-window absolute inset-0 overflow-hidden" style={{ "--i": 0, borderRadius: 10 }}>
|
||
<div className="fill flex h-[38px] items-center justify-between border-b border-black/[.07] px-4 text-[12px]">
|
||
<div className="flex items-center gap-2">
|
||
<span className="font-semibold tracking-tight">{title}</span>
|
||
<span className="text-black/35">/</span>
|
||
<span className="text-black/55">{crumb}</span>
|
||
</div>
|
||
<div className="flex items-center gap-2 text-black/55">{right}</div>
|
||
</div>
|
||
{children}
|
||
</div>
|
||
);
|
||
|
||
const Dim = ({ children, style }) => <span className="dim" style={style}>{children}</span>;
|
||
|
||
const Callout = ({ children, style, side = "left" }) => (
|
||
<div className="callout absolute z-10 flex items-center gap-2" style={style}>
|
||
{side === "right" && <span className="h-px w-8 bg-[var(--blaze)]" />}
|
||
<div className="rounded-md bg-[var(--ink)] px-3 py-2 text-[12px] leading-tight text-white shadow-xl">{children}</div>
|
||
{side === "left" && <span className="h-px w-8 bg-[var(--blaze)]" />}
|
||
</div>
|
||
);
|
||
|
||
const Live = ({ label = "Live" }) => (
|
||
<span className="inline-flex items-center gap-1.5 font-medium">
|
||
<span className="h-1.5 w-1.5 rounded-full bg-[var(--blaze)] animate-pulse" />
|
||
{label}
|
||
</span>
|
||
);
|
||
|
||
/* ---------------- Ledgerline : treasury ---------------- */
|
||
export function LedgerUI({ step }) {
|
||
const { isRtl } = useLang();
|
||
const pts = [62, 58, 64, 60, 70, 66, 74, 71, 80, 77, 86, 83, 92, 96];
|
||
const path = pts.map((v, i) => `${i ? "L" : "M"}${(i / (pts.length - 1)) * 420} ${140 - v * 1.25}`).join(" ");
|
||
const rows = isRtl
|
||
? [
|
||
["شرکت نورثویند", "€ ۱,۲۰۴,۰۰۰.۰۰"],
|
||
["آکیرو کیکی", "¥ ۸۸,۴۰۰,۰۰۰"],
|
||
["هالورسن آاس", "kr ۳,۹۰۰,۵۰۰"],
|
||
["اورتیز و واله", "$ ۶۴۰,۲۱۰.۰۰"],
|
||
["بانکا لومه", "€ ۹۲,۰۰۰.۰۰"],
|
||
]
|
||
: [
|
||
["Northwind GmbH", "€ 1,204,000.00"],
|
||
["Akiro KK", "¥ 88,400,000"],
|
||
["Halvorsen AS", "kr 3,900,500"],
|
||
["Ortiz & Vale", "$ 640,210.00"],
|
||
["Banca Lume", "€ 92,000.00"],
|
||
];
|
||
|
||
return (
|
||
<div className="ui absolute inset-0" data-step={step}>
|
||
<Win
|
||
title={isRtl ? "لجرلاین" : "Ledgerline"}
|
||
crumb={isRtl ? "وضعیت نقدینگی" : "Cash position"}
|
||
right={
|
||
<>
|
||
<span className="mono">EUR</span>
|
||
<span className="text-black/25">·</span>
|
||
<Live label={isRtl ? "زنده" : "Live"} />
|
||
</>
|
||
}
|
||
>
|
||
<div className={`absolute ${isRtl ? "right-4" : "left-4"} top-[54px] w-[440px]`}>
|
||
<div className="bp bp-sunk relative h-[100px] px-5 py-4" style={{ "--i": 1 }}>
|
||
<div className="fill text-[11px] uppercase tracking-[.08em] text-black/55 mono">
|
||
{isRtl ? "تراز تجمیعی · ۲۳ حساب · ۹ ارز بینالملل" : "Consolidated · 23 accounts · 9 currencies"}
|
||
</div>
|
||
<div className="fill mt-2 text-[38px] font-semibold tracking-[-0.04em] tabular-nums dir-ltr text-start">
|
||
€48,213,940<span className="text-black/35">.12</span>
|
||
</div>
|
||
<Dim style={{ [isRtl ? "left" : "right"]: 8, top: 6 }}>h 100</Dim>
|
||
</div>
|
||
<div className="bp relative mt-3 h-[280px] p-4" style={{ "--i": 2 }}>
|
||
<div className="fill flex justify-between text-[11px] text-black/55 mono">
|
||
<span>{isRtl ? "وضعیت ۹۰ روزه" : "90 DAY POSITION"}</span>
|
||
<span className="text-emerald-700 font-semibold dir-ltr">+12.8%</span>
|
||
</div>
|
||
<svg className="fill mt-3" width="408" height="196" viewBox="0 0 420 150" preserveAspectRatio="none">
|
||
{[0, 1, 2, 3].map((g) => (
|
||
<line key={g} x1="0" x2="420" y1={g * 40 + 10} y2={g * 40 + 10} stroke="rgba(18,20,19,.07)" />
|
||
))}
|
||
<path d={`${path} L420 150 L0 150Z`} fill="rgba(255,91,20,.08)" />
|
||
<path d={path} fill="none" stroke="var(--ink)" strokeWidth="1.6" />
|
||
<circle cx="420" cy={140 - 96 * 1.25} r="4" fill="var(--blaze)" />
|
||
</svg>
|
||
<div className="fill mt-1 flex justify-between text-[11px] text-black/50 mono">
|
||
<span>{isRtl ? "تیر" : "JUL"}</span>
|
||
<span>{isRtl ? "مرداد" : "AUG"}</span>
|
||
<span>{isRtl ? "شهریور" : "SEP"}</span>
|
||
</div>
|
||
<Dim style={{ [isRtl ? "right" : "left"]: 8, bottom: 6 }}>w 440 · chart 90d</Dim>
|
||
</div>
|
||
</div>
|
||
<div className={`bp absolute ${isRtl ? "left-4" : "right-4"} top-[54px] h-[392px] w-[232px] p-3`} style={{ "--i": 3 }}>
|
||
<div className="fill mb-2 flex justify-between text-[11px] text-black/55 mono">
|
||
<span>{isRtl ? "تسویهها" : "SETTLEMENTS"}</span>
|
||
<span>{isRtl ? "امروز ۲۱۴" : "today 214"}</span>
|
||
</div>
|
||
{rows.map(([n, a], i) => (
|
||
<div
|
||
key={n}
|
||
className={`live fill mb-2.5 rounded-md border px-2.5 py-2 ${
|
||
step === 2 && i === 2 ? "border-[var(--blaze)] bg-[rgba(255,91,20,.08)]" : "border-black/[.06] bg-white"
|
||
}`}
|
||
style={{ "--i": 4 + i }}
|
||
>
|
||
<div className="flex justify-between text-[12px] font-medium">
|
||
<span>{n}</span>
|
||
</div>
|
||
<div className="mt-0.5 flex justify-between text-[11px] tabular-nums text-black/60">
|
||
<span className="dir-ltr text-start">{a}</span>
|
||
<span className={step === 2 && i === 2 ? "text-[var(--blaze)] font-medium" : ""}>
|
||
{step === 2 && i === 2
|
||
? isRtl ? "تسویه ۱۴۰ms" : "settled 138ms"
|
||
: i < 2
|
||
? isRtl ? "در انتظار" : "pending"
|
||
: isRtl ? "تسویه شد" : "settled"}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
<Dim style={{ [isRtl ? "left" : "right"]: 8, bottom: 6 }}>w 232 · list ×n</Dim>
|
||
</div>
|
||
</Win>
|
||
<Callout style={{ [isRtl ? "left" : "right"]: 254, top: 200 }}>
|
||
{isRtl ? "تاخیر p99 تسویه" : "p99 settlement"}
|
||
<br />
|
||
<b className="text-[17px] text-[var(--blaze)] dir-ltr">140 ms</b>
|
||
</Callout>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ---------------- Tidewater : berth planning ---------------- */
|
||
export function TideUI({ step }) {
|
||
const { isRtl } = useLang();
|
||
const ships = [
|
||
{ b: 0, x: 30, w: 170, n: "MSC Aurelia", t: "ETA 02:10" },
|
||
{ b: 0, x: 240, w: 210, n: "Kobe Express", t: "ETA 09:40" },
|
||
{ b: 1, x: 90, w: 150, n: "Nordlys", t: "ETA 04:00" },
|
||
{ b: 1, x: 260, w: 140, n: "Cap Ferro", t: "ETA 11:20", conflict: true },
|
||
{ b: 2, x: 20, w: 260, n: "Ever Lumen", t: "ETA 00:30" },
|
||
{ b: 3, x: 300, w: 180, n: "Saga Viking", t: "ETA 13:00" },
|
||
{ b: 4, x: 60, w: 200, n: "Atlantic Wren", t: "ETA 03:15" },
|
||
{ b: 4, x: 300, w: 120, n: "Hanse 7", t: "ETA 12:30" },
|
||
];
|
||
const tabs = isRtl ? ["اسکلهها", "جرثقیلها", "محوطه کانتینر", "گیتها"] : ["Berths", "Cranes", "Yard", "Gates"];
|
||
|
||
return (
|
||
<div className="ui absolute inset-0" data-step={step}>
|
||
<Win
|
||
title={isRtl ? "تایدواتر" : "Tidewater"}
|
||
crumb={isRtl ? "ترمینال غربی · پنجشنبه ۱۷ مهر" : "Terminal West · Thu 8 Oct"}
|
||
right={
|
||
<>
|
||
<span className="mono">{isRtl ? "۳۲ شناور" : "32 calls"}</span>
|
||
<span className="text-black/25">·</span>
|
||
<Live label={isRtl ? "زنده" : "Live"} />
|
||
</>
|
||
}
|
||
>
|
||
<div className="absolute left-4 right-4 top-[54px] flex h-[36px] items-center gap-2">
|
||
{tabs.map((t, i) => (
|
||
<div
|
||
key={t}
|
||
className={`bp fill flex h-full items-center px-3 text-[12px] ${
|
||
i === 0 ? "font-semibold" : "text-black/50"
|
||
}`}
|
||
style={{ "--i": 1 + i }}
|
||
>
|
||
{t}
|
||
</div>
|
||
))}
|
||
<div className={`bp ${isRtl ? "mr-auto" : "ml-auto"} flex h-full items-center px-3 text-[12px]`} style={{ "--i": 5 }}>
|
||
<span className="fill mono text-black/60">
|
||
{isRtl ? `بهرهوری ${step === 2 ? "۹۱٪" : "۷۴٪"}` : `utilisation ${step === 2 ? "91%" : "74%"}`}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div className="bp absolute bottom-4 left-4 right-4 top-[102px] overflow-hidden" style={{ "--i": 6 }}>
|
||
<div className="fill absolute left-[86px] right-0 top-0 flex h-[28px] items-center justify-between border-b border-black/[.06] pr-4 text-[10px] text-black/40 mono dir-ltr">
|
||
{["00", "04", "08", "12", "16", "20", "24"].map((h) => (
|
||
<span key={h}>{h}:00</span>
|
||
))}
|
||
</div>
|
||
{[0, 1, 2, 3, 4].map((b) => (
|
||
<div
|
||
key={b}
|
||
className="absolute left-0 right-0 flex items-center border-b border-black/[.05]"
|
||
style={{ top: 28 + b * 62, height: 62 }}
|
||
>
|
||
<span className="fill w-[86px] pl-4 text-[12px] font-medium" style={{ "--i": 7 + b }}>
|
||
{isRtl ? `اسکله ${b + 1}` : `Berth ${b + 1}`}
|
||
</span>
|
||
</div>
|
||
))}
|
||
<div className="absolute bottom-0 top-[28px] w-px bg-[var(--blaze)]" style={{ left: 86 + 245 }}>
|
||
<span className="fill absolute -left-[18px] -top-[1px] rounded bg-[var(--blaze)] px-1 text-[9px] text-white mono">
|
||
{isRtl ? "اکنون" : "NOW"}
|
||
</span>
|
||
</div>
|
||
{ships.map((s, i) => {
|
||
const moved = step === 2 && s.conflict;
|
||
return (
|
||
<div
|
||
key={s.n}
|
||
className={`live bp absolute flex flex-col justify-center px-2.5 ${
|
||
s.conflict && step === 1 ? "!border-[var(--blaze)] !bg-[rgba(255,91,20,.12)]" : ""
|
||
} ${moved ? "!border-[var(--ink)] !bg-[var(--ink)] text-white" : ""}`}
|
||
style={{
|
||
"--i": 10 + i,
|
||
left: 86 + s.x,
|
||
width: s.w,
|
||
top: 28 + s.b * 62 + 10,
|
||
height: 42,
|
||
transform: moved ? `translate(${150 - s.x}px, ${2 * 62}px)` : "none",
|
||
}}
|
||
>
|
||
<span className="fill text-[12px] font-medium leading-tight">{s.n}</span>
|
||
<span className={`fill text-[10px] mono ${moved ? "text-white/70" : "text-black/50"}`}>
|
||
{moved
|
||
? isRtl ? "تغییر اسکله · بدون توقف" : "re-berthed · 0 wait"
|
||
: s.conflict && step === 1
|
||
? isRtl ? "تداخل ۲س ۱۰د" : "overlap 2h 10m"
|
||
: s.t}
|
||
</span>
|
||
</div>
|
||
);
|
||
})}
|
||
<Dim style={{ left: 92, bottom: 8 }}>
|
||
{isRtl ? "ارتفاع ۶۲ · ۵ اسکله · محور ۲۴ ساعته" : "row h 62 · 5 berths · 24h axis"}
|
||
</Dim>
|
||
</div>
|
||
</Win>
|
||
<Callout side="right" style={{ left: 450, top: 236 }}>
|
||
{isRtl ? "معطلی اسکله" : "berth idle time"}
|
||
<br />
|
||
<b className="text-[18px] text-[var(--blaze)] dir-ltr">−31%</b>
|
||
</Callout>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ---------------- Quire : contract review ---------------- */
|
||
export function QuireUI({ step }) {
|
||
const { isRtl } = useLang();
|
||
const para = (ws, key, hl) => (
|
||
<div
|
||
key={key}
|
||
className={`fill mb-3 rounded-sm ${hl ? "-mx-2 px-2 py-1.5 live" : ""}`}
|
||
style={{ background: hl ? (step === 2 ? "transparent" : "rgba(255,91,20,.1)") : "transparent" }}
|
||
>
|
||
{ws.map((w, i) => (
|
||
<Bar key={i} w={w} h={5} className="mb-[7px]" />
|
||
))}
|
||
</div>
|
||
);
|
||
|
||
return (
|
||
<div className="ui absolute inset-0" data-step={step}>
|
||
<Win
|
||
title={isRtl ? "کوایر" : "Quire"}
|
||
crumb={isRtl ? "قرارداد تامین کالا · نسخه ۴" : "Northwind Supply · MSA v4"}
|
||
right={
|
||
<>
|
||
<span className="mono">{isRtl ? "۴۸ ص" : "48 pp"}</span>
|
||
<span className="text-black/25">·</span>
|
||
<span>{isRtl ? "شیوهنامه: استاندارد" : "Playbook: Standard"}</span>
|
||
</>
|
||
}
|
||
>
|
||
<div className={`bp absolute bottom-4 ${isRtl ? "right-4" : "left-4"} top-[54px] w-[420px] overflow-hidden px-7 py-6`} style={{ "--i": 1 }}>
|
||
<div className="fill mono text-[10px] text-black/50">
|
||
{isRtl ? "بند ۱۴ · مسئولیت طرفین" : "§ 14 · LIABILITY"}
|
||
</div>
|
||
<div className="fill mb-4 mt-1 text-[16px] font-semibold tracking-tight">
|
||
{isRtl ? "۱۴.۲ سقف مسئولیت مدنی" : "14.2 Limitation of liability"}
|
||
</div>
|
||
{para([360, 340, 352, 210], "a")}
|
||
<div className="fill relative mb-3 text-[12.5px] leading-[1.6] text-black/80">
|
||
{isRtl ? (
|
||
<>
|
||
مجموع مسئولیت تامینکننده نباید از{" "}
|
||
<span className={`live ${step === 2 ? "line-through decoration-[var(--blaze)] text-black/35" : "bg-[rgba(255,91,20,.16)]"}`}>
|
||
کارمزدهای پرداختشده در سه (۳) ماه گذشته
|
||
</span>
|
||
{step === 2 && (
|
||
<span className="font-semibold text-[var(--blaze)]">
|
||
{" "}دو برابر (۲×) مجموع کارمزدهای پرداختشده در دوازده (۱۲) ماه گذشته فراتر رود.
|
||
</span>
|
||
)}
|
||
</>
|
||
) : (
|
||
<>
|
||
Supplier’s aggregate liability shall not exceed{" "}
|
||
<span className={`live ${step === 2 ? "line-through decoration-[var(--blaze)] text-black/35" : "bg-[rgba(255,91,20,.16)]"}`}>
|
||
fees paid in the preceding three (3) months
|
||
</span>
|
||
{step === 2 && (
|
||
<span className="font-medium text-[var(--blaze)]">
|
||
{" "}two times (2×) the fees paid in the preceding twelve (12) months
|
||
</span>
|
||
)}
|
||
.
|
||
</>
|
||
)}
|
||
</div>
|
||
{para([350, 362, 300], "b")}
|
||
{para([340, 356, 120], "c")}
|
||
<Dim style={{ [isRtl ? "left" : "right"]: 10, top: 8 }}>page w 420 · measure 62ch</Dim>
|
||
</div>
|
||
<div className={`absolute bottom-4 ${isRtl ? "left-4" : "right-4"} top-[54px] w-[244px]`}>
|
||
<div className="bp relative h-[96px] p-3" style={{ "--i": 2 }}>
|
||
<div className="fill mono text-[10px] text-black/50">
|
||
{isRtl ? "بازبینی خودکار" : "REVIEW"}
|
||
</div>
|
||
<div className="fill mt-1 flex items-baseline gap-2">
|
||
<span className="text-[28px] font-semibold tracking-[-0.04em] tabular-nums">
|
||
{step === 2 ? "0" : "3"}
|
||
</span>
|
||
<span className="text-[12px] text-black/60">
|
||
{step === 2
|
||
? isRtl ? "انحراف باز" : "open deviations"
|
||
: isRtl ? "انحراف از شیوهنامه" : "deviations from playbook"}
|
||
</span>
|
||
</div>
|
||
<div className="fill mt-2 h-1 overflow-hidden rounded-full bg-black/[.07]">
|
||
<div
|
||
className="live h-full bg-[var(--ink)]"
|
||
style={{ width: step === 2 ? "100%" : "86%", transition: "width 900ms" }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
{[
|
||
[
|
||
"14.2",
|
||
isRtl ? "سقف مسئولیت کمتر از ۱۲ ماه" : "Liability cap below 12-month fees",
|
||
true,
|
||
],
|
||
[
|
||
"9.1",
|
||
isRtl ? "تمدید خودکار بدون اعلام قبلی" : "Auto-renewal without notice",
|
||
false,
|
||
],
|
||
[
|
||
"21.4",
|
||
isRtl ? "قانون حاکم: نامشخص" : "Governing law: unlisted",
|
||
false,
|
||
],
|
||
].map(([c, t, main], i) => (
|
||
<div
|
||
key={c}
|
||
className={`bp live relative mt-2 p-3 ${main && step >= 1 ? "!border-[var(--blaze)]" : ""}`}
|
||
style={{ "--i": 3 + i }}
|
||
>
|
||
<div className="fill flex items-center justify-between">
|
||
<span className="mono text-[11px] text-black/50">§ {c}</span>
|
||
<span
|
||
className={`text-[10px] mono ${
|
||
step === 2 ? "text-black/50" : main ? "text-[var(--blaze)] font-semibold" : "text-black/50"
|
||
}`}
|
||
>
|
||
{step === 2
|
||
? isRtl ? "حل شد" : "resolved"
|
||
: main
|
||
? isRtl ? "ریسک بالا" : "high"
|
||
: isRtl ? "عادی" : "low"}
|
||
</span>
|
||
</div>
|
||
<div className="fill mt-1 text-[12px] font-medium leading-snug">{t}</div>
|
||
</div>
|
||
))}
|
||
<Dim style={{ [isRtl ? "left" : "right"]: 6, bottom: -14 }}>panel w 244</Dim>
|
||
</div>
|
||
</Win>
|
||
<Callout side="right" style={{ left: 214, top: 64 }}>
|
||
{isRtl ? "میانگین زمان بازبینی" : "median review"}
|
||
<br />
|
||
<b className="text-[17px] text-[var(--blaze)] dir-ltr">
|
||
{isRtl ? "۶ ساعت ← ۴۰ دقیقه" : "6 h → 40 min"}
|
||
</b>
|
||
</Callout>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ---------------- Halden : clinical trial ops ---------------- */
|
||
export function HaldenUI({ step }) {
|
||
const { isRtl } = useLang();
|
||
const sites = [
|
||
[60, 70, 9],
|
||
[120, 50, 6],
|
||
[170, 110, 12],
|
||
[90, 160, 7],
|
||
[230, 80, 5],
|
||
[280, 140, 10],
|
||
[210, 200, 8],
|
||
[330, 60, 4],
|
||
[350, 190, 9],
|
||
[140, 230, 6],
|
||
[300, 250, 11],
|
||
[390, 120, 5],
|
||
[60, 250, 4],
|
||
[250, 30, 6],
|
||
];
|
||
const flagged = 5;
|
||
|
||
return (
|
||
<div className="ui absolute inset-0" data-step={step}>
|
||
<Win
|
||
title={isRtl ? "هالدن" : "Halden"}
|
||
crumb={isRtl ? "مطالعه HLD-301 · فاز ۳" : "HLD-301 · Phase III"}
|
||
right={
|
||
<>
|
||
<span className="mono">{isRtl ? "۱۴ مرکز" : "14 sites"}</span>
|
||
<span className="text-black/25">·</span>
|
||
<Live label={isRtl ? "زنده" : "Live"} />
|
||
</>
|
||
}
|
||
>
|
||
<div className={`bp bp-sunk absolute bottom-4 ${isRtl ? "right-4" : "left-4"} top-[54px] w-[440px] overflow-hidden`} style={{ "--i": 1 }}>
|
||
<svg className="fill absolute inset-0" width="440" height="390">
|
||
{Array.from({ length: 12 }).map((_, i) => (
|
||
<line key={"v" + i} x1={i * 40} x2={i * 40} y1="0" y2="390" stroke="rgba(18,20,19,.05)" />
|
||
))}
|
||
{Array.from({ length: 10 }).map((_, i) => (
|
||
<line key={"h" + i} y1={i * 40} y2={i * 40} x1="0" x2="440" stroke="rgba(18,20,19,.05)" />
|
||
))}
|
||
<path
|
||
d="M40 300 C 120 260, 160 300, 230 270 S 360 300, 420 250"
|
||
fill="none"
|
||
stroke="rgba(18,20,19,.12)"
|
||
strokeDasharray="3 4"
|
||
/>
|
||
</svg>
|
||
{sites.map(([x, y, r], i) => (
|
||
<span
|
||
key={i}
|
||
className={`fill absolute rounded-full ${
|
||
step === 2 && i === flagged ? "pulse bg-[var(--blaze)]" : "bg-[var(--ink)]"
|
||
}`}
|
||
style={{
|
||
"--i": 2 + i * 0.4,
|
||
left: x + 10,
|
||
top: y + 30,
|
||
width: r + 4,
|
||
height: r + 4,
|
||
opacity: step === 0 ? 0 : 0.85,
|
||
}}
|
||
/>
|
||
))}
|
||
<div className={`fill absolute ${isRtl ? "right-4" : "left-4"} top-3 mono text-[10px] text-black/50`}>
|
||
{isRtl ? "پذیرش به تفکیک مرکز بالینی · نقطه = بیماران" : "ENROLMENT BY SITE · DOT = PATIENTS"}
|
||
</div>
|
||
<div className={`fill absolute bottom-3 ${isRtl ? "right-4" : "left-4"} flex gap-4 text-[11px] text-black/60`}>
|
||
<span>{isRtl ? "۴۱۲ پذیرششده" : "412 enrolled"}</span>
|
||
<span>{isRtl ? "هدف ۶۰۰" : "target 600"}</span>
|
||
<span>{isRtl ? "روز ۲۱۴" : "day 214"}</span>
|
||
</div>
|
||
<Dim style={{ [isRtl ? "left" : "right"]: 8, top: 8 }}>map 440 × 390</Dim>
|
||
</div>
|
||
<div className={`bp absolute bottom-4 ${isRtl ? "left-4" : "right-4"} top-[54px] w-[232px] p-3`} style={{ "--i": 2 }}>
|
||
<div className="fill mb-2 mono text-[10px] text-black/50">
|
||
{isRtl ? "سیگنالها و هشدارها" : "SIGNALS"}
|
||
</div>
|
||
{[
|
||
[
|
||
isRtl ? "مرکز ۰۶ · لیدن" : "Site 06 · Leiden",
|
||
isRtl ? "ریسک انحراف از پروتکل" : "Visit window drift",
|
||
0.72,
|
||
true,
|
||
],
|
||
[isRtl ? "مرکز ۰۳ · پورتو" : "Site 03 · Porto", isRtl ? "مطابق برنامه" : "On track", 0.9],
|
||
[isRtl ? "مرکز ۱۱ · لیون" : "Site 11 · Lyon", isRtl ? "مطابق برنامه" : "On track", 0.64],
|
||
[isRtl ? "مرکز ۰۹ · گراتس" : "Site 09 · Graz", isRtl ? "مطابق برنامه" : "On track", 0.58],
|
||
[isRtl ? "مرکز ۰۱ · تورکو" : "Site 01 · Turku", isRtl ? "مطابق برنامه" : "On track", 0.81],
|
||
].map(([n, s, v, f], i) => (
|
||
<div
|
||
key={n}
|
||
className={`live fill mb-1.5 rounded-md border px-2.5 py-2 ${
|
||
step === 2 && f ? "border-[var(--blaze)] bg-[rgba(255,91,20,.08)]" : "border-black/[.06] bg-white"
|
||
}`}
|
||
style={{ "--i": 3 + i }}
|
||
>
|
||
<div className="flex justify-between text-[12px] font-medium">
|
||
<span>{n}</span>
|
||
</div>
|
||
<div
|
||
className={`mt-0.5 text-[11px] ${
|
||
step === 2 && f ? "text-[var(--blaze)] font-semibold" : "text-black/55"
|
||
}`}
|
||
>
|
||
{step === 2 && f ? (isRtl ? "ریسک انحراف · ۳ بیمار" : "Deviation risk · 3 pts") : s}
|
||
</div>
|
||
<div className="mt-1.5 h-1 rounded-full bg-black/[.06]">
|
||
<div className="h-full rounded-full bg-[var(--ink)]" style={{ width: `${v * 100}%` }} />
|
||
</div>
|
||
</div>
|
||
))}
|
||
<Dim style={{ [isRtl ? "right" : "left"]: 8, bottom: 6 }}>w 232 · signal ×n</Dim>
|
||
</div>
|
||
</Win>
|
||
<Callout side="right" style={{ left: 312, top: 158 }}>
|
||
{isRtl ? "کشف انحرافات" : "deviations caught"}
|
||
<br />
|
||
<b className="text-[17px] text-[var(--blaze)] dir-ltr">
|
||
{isRtl ? "۹ روز زودتر" : "9 days earlier"}
|
||
</b>
|
||
</Callout>
|
||
</div>
|
||
);
|
||
}
|