Files
landing-kerf/src/components/ProductUIs.jsx
T

524 lines
23 KiB
React
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 { 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>
);
}