makearmy-app/app/laser-toolkit/dpi-lpi-dpcm/page.tsx
2025-09-22 10:37:53 -04:00

113 lines
3.7 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import ToolShell from "@/components/toolkit/ToolShell";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
function num(v: string) {
const n = parseFloat(v);
return Number.isFinite(n) ? n : 0;
}
export default function Page() {
const [dpi, setDpi] = useState("300");
const [lpi, setLpi] = useState("300");
const [dpcm, setDpcm] = useState("118.11");
const [active, setActive] = useState<"dpi" | "lpi" | "dpcm">("dpi");
// keep all three in sync based on the most recently edited field
useEffect(() => {
const D = num(dpi), L = num(lpi), C = num(dpcm);
if (active === "dpi") {
const d = Math.max(1e-9, D);
setDpcm((d / 2.54).toFixed(5));
setLpi(D.toFixed(2)); // LPI≈DPI for raster row spacing (workflow convention)
} else if (active === "lpi") {
const l = Math.max(1e-9, L);
setDpi(L.toFixed(2));
setDpcm((L / 2.54).toFixed(5));
} else {
const c = Math.max(1e-9, C);
setDpi((c * 2.54).toFixed(2));
setLpi((c * 2.54).toFixed(2));
}
}, [dpi, lpi, dpcm, active]);
const gapFromDpiMm = 25.4 / Math.max(1e-9, num(dpi));
const gapFromLpiMm = 25.4 / Math.max(1e-9, num(lpi));
return (
<ToolShell title="DPI / LPI / DPCM Converter">
<Card>
<CardHeader>
<CardTitle className="text-base">Values</CardTitle>
</CardHeader>
<CardContent className="grid gap-3 sm:grid-cols-3">
<label className="text-[11px] sm:text-xs">
<div className="mb-1 text-muted-foreground">DPI</div>
<Input
inputMode="decimal"
value={dpi}
onChange={(e) => {
setActive("dpi");
setDpi(e.target.value);
}}
/>
</label>
<label className="text-[11px] sm:text-xs">
<div className="mb-1 text-muted-foreground">LPI</div>
<Input
inputMode="decimal"
value={lpi}
onChange={(e) => {
setActive("lpi");
setLpi(e.target.value);
}}
/>
</label>
<label className="text-[11px] sm:text-xs">
<div className="mb-1 text-muted-foreground">DPCM</div>
<Input
inputMode="decimal"
value={dpcm}
onChange={(e) => {
setActive("dpcm");
setDpcm(e.target.value);
}}
/>
</label>
</CardContent>
</Card>
<Card className="mt-4">
<CardHeader>
<CardTitle className="text-base">Derived spacing</CardTitle>
</CardHeader>
<CardContent className="grid gap-3 sm:grid-cols-3">
<div>
<div className="text-sm text-muted-foreground">Pixel/line gap from DPI</div>
<div className="text-lg">{gapFromDpiMm.toFixed(4)} mm</div>
<div className="text-xs text-muted-foreground">{(gapFromDpiMm * 1000).toFixed(1)} µm</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Line gap from LPI</div>
<div className="text-lg">{gapFromLpiMm.toFixed(4)} mm</div>
<div className="text-xs text-muted-foreground">{(gapFromLpiMm * 1000).toFixed(1)} µm</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Pixels/cm from DPCM</div>
<div className="text-lg">{num(dpcm).toFixed(2)} px/cm</div>
<div className="text-xs text-muted-foreground">
{(num(dpcm) * 2.54).toFixed(2)} px/in
</div>
</div>
</CardContent>
</Card>
</ToolShell>
);
}