203 lines
7.8 KiB
TypeScript
203 lines
7.8 KiB
TypeScript
import { Link } from "react-router-dom";
|
|
|
|
const workflow = [
|
|
{
|
|
label: "Input",
|
|
title: "Choose a tool",
|
|
copy: "Paste text, pick a file, or adjust options in a generated tool panel.",
|
|
icon: (
|
|
<path d="M12 5v14M5 12h14" />
|
|
),
|
|
},
|
|
{
|
|
label: "Run",
|
|
title: "Process locally",
|
|
copy: "The plugin runs in your browser using JavaScript, workers, Canvas, or PDF libraries.",
|
|
icon: (
|
|
<>
|
|
<path d="M12 3v3M12 18v3M3 12h3M18 12h3" />
|
|
<circle cx="12" cy="12" r="4" />
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
label: "Output",
|
|
title: "Copy or download",
|
|
copy: "Results appear immediately in the page. Files stay on the device unless you export them.",
|
|
icon: (
|
|
<>
|
|
<path d="M12 4v11" />
|
|
<path d="m7 10 5 5 5-5" />
|
|
<path d="M5 20h14" />
|
|
</>
|
|
),
|
|
},
|
|
];
|
|
|
|
const principles = [
|
|
{
|
|
title: "No upload step",
|
|
copy: "Plimi does not send your inputs to an application server for conversion.",
|
|
},
|
|
{
|
|
title: "Plugin boundaries",
|
|
copy: "Each utility declares its own inputs, options, permissions, and result type.",
|
|
},
|
|
{
|
|
title: "Responsive execution",
|
|
copy: "Heavier work can move off the main interface thread so the page stays usable.",
|
|
},
|
|
{
|
|
title: "Explicit results",
|
|
copy: "Tools return structured outputs: copied text, rendered previews, or downloadable files.",
|
|
},
|
|
];
|
|
|
|
const layers = [
|
|
{ name: "Tool manifest", detail: "Inputs, options, permissions" },
|
|
{ name: "Generated UI", detail: "Forms, sliders, selects, validation" },
|
|
{ name: "Runner", detail: "Browser APIs, workers, local libraries" },
|
|
{ name: "Result panel", detail: "Preview, copy, download" },
|
|
];
|
|
|
|
function IconFrame({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<span className="inline-flex h-10 w-10 items-center justify-center rounded-[10px] border border-[var(--p-border)] bg-[var(--p-chip)] text-[var(--p-text)]">
|
|
<svg
|
|
width="19"
|
|
height="19"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
aria-hidden="true"
|
|
>
|
|
{children}
|
|
</svg>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export function HowItWorksPage() {
|
|
return (
|
|
<div className="mx-auto flex max-w-[1120px] flex-col gap-10 pb-16 animate-plimi-slide">
|
|
<section className="grid grid-cols-1 gap-8 md:grid-cols-[0.95fr_1.05fr] md:items-end">
|
|
<div className="flex flex-col gap-4">
|
|
<span className="font-mono text-[11px] uppercase tracking-[0.12em] text-[var(--p-muted)]">
|
|
browser-first architecture
|
|
</span>
|
|
<h1 className="m-0 max-w-[620px] font-sans text-4xl font-bold leading-[1.02] tracking-tight text-[var(--p-text)] text-balance md:text-[56px]">
|
|
Your files stay where they started.
|
|
</h1>
|
|
<p className="m-0 max-w-[560px] text-base leading-relaxed text-[var(--p-muted)] text-pretty">
|
|
Plimi is a collection of small local utilities. The interface loads the tool, runs the
|
|
work in the browser, then hands the result back to you without a server round trip.
|
|
</p>
|
|
</div>
|
|
|
|
<div
|
|
className="rounded-[20px] border border-[var(--p-border)] bg-[var(--p-surface)] p-4 md:p-5"
|
|
style={{
|
|
boxShadow:
|
|
"0 18px 38px -28px var(--p-shadow-soft), 0 1px 0 0 var(--p-shadow-inset)",
|
|
}}
|
|
>
|
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3 md:grid-cols-1 lg:grid-cols-3">
|
|
{workflow.map((step, index) => (
|
|
<div
|
|
key={step.label}
|
|
className="relative flex min-h-[170px] flex-col gap-4 rounded-[16px] border border-[var(--p-border)] bg-[var(--p-surface-2)] p-5"
|
|
>
|
|
<div className="flex items-center justify-between gap-3">
|
|
<IconFrame>{step.icon}</IconFrame>
|
|
<span className="font-mono text-[11px] uppercase tracking-[0.12em] text-[var(--p-muted)]">
|
|
{String(index + 1).padStart(2, "0")}
|
|
</span>
|
|
</div>
|
|
<div className="flex flex-col gap-1.5">
|
|
<span className="font-mono text-[11px] uppercase tracking-[0.12em] text-[var(--p-accent)]">
|
|
{step.label}
|
|
</span>
|
|
<h2 className="m-0 font-sans text-[17px] font-semibold tracking-tight text-[var(--p-text)]">
|
|
{step.title}
|
|
</h2>
|
|
<p className="m-0 text-sm leading-relaxed text-[var(--p-muted)]">{step.copy}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="grid grid-cols-1 gap-4 md:grid-cols-4">
|
|
{principles.map((item) => (
|
|
<article
|
|
key={item.title}
|
|
className="flex min-h-[150px] flex-col justify-between gap-5 rounded-[16px] border border-[var(--p-border)] bg-[var(--p-surface)] p-5"
|
|
>
|
|
<h2 className="m-0 font-sans text-[16px] font-semibold tracking-tight text-[var(--p-text)]">
|
|
{item.title}
|
|
</h2>
|
|
<p className="m-0 text-sm leading-relaxed text-[var(--p-muted)]">{item.copy}</p>
|
|
</article>
|
|
))}
|
|
</section>
|
|
|
|
<section className="grid grid-cols-1 gap-6 rounded-[20px] border border-[var(--p-border)] bg-[var(--p-surface-2)] p-5 md:grid-cols-[0.75fr_1.25fr] md:p-7">
|
|
<div className="flex flex-col gap-3">
|
|
<span className="font-mono text-[11px] uppercase tracking-[0.12em] text-[var(--p-muted)]">
|
|
plugin model
|
|
</span>
|
|
<h2 className="m-0 font-sans text-2xl font-bold tracking-tight text-[var(--p-text)]">
|
|
One shell, many tools.
|
|
</h2>
|
|
<p className="m-0 text-sm leading-relaxed text-[var(--p-muted)]">
|
|
Plimi keeps the application shell simple. Each tool contributes a compact manifest and
|
|
a runner, so new utilities can share the same dependable interface.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
{layers.map((layer, index) => (
|
|
<div
|
|
key={layer.name}
|
|
className="flex items-start gap-4 rounded-[14px] border border-[var(--p-border)] bg-[var(--p-surface)] p-4"
|
|
>
|
|
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-[var(--p-chip)] font-mono text-[11px] text-[var(--p-muted)]">
|
|
{index + 1}
|
|
</span>
|
|
<div className="flex flex-col gap-1">
|
|
<h3 className="m-0 font-sans text-sm font-semibold text-[var(--p-text)]">
|
|
{layer.name}
|
|
</h3>
|
|
<p className="m-0 text-[13px] leading-relaxed text-[var(--p-muted)]">
|
|
{layer.detail}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
<section className="flex flex-col items-start justify-between gap-4 border-t border-[var(--p-border)] pt-7 md:flex-row md:items-center">
|
|
<div className="flex flex-col gap-1">
|
|
<h2 className="m-0 font-sans text-xl font-semibold tracking-tight text-[var(--p-text)]">
|
|
Ready to run something?
|
|
</h2>
|
|
<p className="m-0 text-sm text-[var(--p-muted)]">
|
|
Pick a utility and the same local workflow applies.
|
|
</p>
|
|
</div>
|
|
<Link
|
|
to="/tools"
|
|
className="inline-flex w-full items-center justify-center rounded-[12px] bg-[var(--p-accent)] px-5 py-3.5 font-sans text-sm font-semibold tracking-tight text-[var(--p-accent-ink)] no-underline transition-[filter] hover:brightness-110 md:w-auto md:rounded-[10px] md:py-3"
|
|
>
|
|
Browse tools
|
|
</Link>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|