ready when you are
diff --git a/src/index.css b/src/index.css
index 09e388c..3a7a5a0 100644
--- a/src/index.css
+++ b/src/index.css
@@ -74,6 +74,16 @@
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
+/* Staggered tile reveal (directory) */
+@keyframes plimi-rise {
+ from { opacity: 0; transform: translateY(14px) scale(0.985); }
+ to { opacity: 1; transform: translateY(0) scale(1); }
+}
+/* Mobile menu sheet drop */
+@keyframes plimi-drop {
+ from { opacity: 0; transform: translateY(-8px); }
+ to { opacity: 1; transform: translateY(0); }
+}
.animate-plimi-fade {
animation: plimi-fade 0.18s ease-out;
@@ -83,6 +93,37 @@
animation: plimi-slide 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
}
+.animate-plimi-rise {
+ animation: plimi-rise 0.42s cubic-bezier(0.2, 0.8, 0.2, 1) both;
+}
+
+.animate-plimi-drop {
+ animation: plimi-drop 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
+}
+
+/* Respect reduced-motion preferences */
+@media (prefers-reduced-motion: reduce) {
+ .animate-plimi-fade,
+ .animate-plimi-slide,
+ .animate-plimi-rise,
+ .animate-plimi-drop {
+ animation: none !important;
+ }
+}
+
+/* Hide scrollbar but keep scroll (mobile chip rows, etc.) */
+.no-scrollbar {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+}
+.no-scrollbar::-webkit-scrollbar {
+ display: none;
+}
+
+/* Honour iOS/Android safe areas at the app edges */
+.pt-safe { padding-top: env(safe-area-inset-top); }
+.pb-safe { padding-bottom: env(safe-area-inset-bottom); }
+
html, body {
margin: 0;
padding: 0;
diff --git a/src/pages/ContributePage.tsx b/src/pages/ContributePage.tsx
new file mode 100644
index 0000000..09233a7
--- /dev/null
+++ b/src/pages/ContributePage.tsx
@@ -0,0 +1,235 @@
+import { Link } from "react-router-dom";
+import type { ReactNode } from "react";
+
+const repoUrl =
+ import.meta.env.VITE_PLIMI_REPO_URL || "https://github.com/your-org/plimi";
+
+const steps = [
+ {
+ title: "Create a tool folder",
+ copy: "Add a new directory under src/tools with an index.ts plugin definition, a run.ts runner, and a run.test.ts test file.",
+ },
+ {
+ title: "Describe the tool",
+ copy: "The manifest declares the id, name, category, tags, input type, output type, permissions, and offline readiness.",
+ },
+ {
+ title: "Implement the runner",
+ copy: "The runner receives typed input, normalized options, and a context for progress, cancellation, and logging.",
+ },
+ {
+ title: "Register and test",
+ copy: "Import the plugin in the registry, add it to the list, then run the unit tests and production build.",
+ },
+];
+
+const apiItems = [
+ {
+ name: "manifest",
+ detail: "Required metadata used by the directory, routing, generated input UI, result display, and permission labels.",
+ },
+ {
+ name: "input",
+ detail: "Supports none, text, files, text-or-files, or grouped fields with per-field labels, placeholders, limits, and examples.",
+ },
+ {
+ name: "optionsSchema",
+ detail: "Optional generated controls for text, number, boolean, select, and slider values.",
+ },
+ {
+ name: "examples",
+ detail: "Optional shared Try example API. Existing manifest.example and field.example values are automatically converted.",
+ },
+ {
+ name: "run",
+ detail: "The async function that performs the work in the browser and returns text, json, table, or downloadable files.",
+ },
+ {
+ name: "customUi",
+ detail: "Optional React UI for complex tools such as image editors, canvas workflows, and richer file interactions.",
+ },
+];
+
+const code = `export const myToolPlugin: PlimiPlugin = {
+ manifest: {
+ id: "my-tool",
+ name: "My Tool",
+ description: "Runs locally in the browser.",
+ category: "developer",
+ version: "1.0.0",
+ tags: ["example"],
+ input: { type: "text", placeholder: "Paste input..." },
+ output: { type: "text" },
+ offlineReady: true,
+ },
+ optionsSchema: {
+ fields: [
+ { type: "boolean", key: "uppercase", label: "Uppercase", defaultValue: false }
+ ],
+ },
+ examples: [
+ {
+ label: "Try example",
+ input: { text: "Hello Plimi" },
+ options: { uppercase: true },
+ },
+ ],
+ capabilities: { worker: false, cancelable: false },
+ permissions: { network: "none", fileSystem: "none", clipboard: "none" },
+ run: runMyTool,
+};`;
+
+function ExternalLink({
+ href,
+ children,
+}: {
+ href: string;
+ children: ReactNode;
+}) {
+ return (
+
+ {children}
+
+ );
+}
+
+export function ContributePage() {
+ return (
+
+
+
+
+ contribute tools
+
+
+ Add useful tools to Plimi.
+
+
+ Plimi V1 supports contributions through the source code. Tools are added as internal
+ plugins, reviewed in Git, and shipped with the app so they remain fast, typed, and
+ browser-local.
+
+ External plugin installation is not available in V1. To contribute, fork the repo,
+ add the tool inside src/tools, register
+ it, test it, and open a pull request.
+
+ Most tools only need a manifest, an optional options schema, and a runner. Plimi can
+ generate the input panel, options controls, example action, and result panel from that
+ contract.
+
+
+
+
+ {apiItems.map((item) => (
+
+
+ {item.name}
+
+
+ {item.detail}
+
+
+ ))}
+
+
+
+
+
+
+ minimal shape
+
+
+ {code}
+
+
+
+
+
+ contribution checklist
+
+
+ {[
+ "Keep execution local. Do not add backend calls for tool processing.",
+ "Prefer generated UI unless the workflow needs a custom canvas or preview.",
+ "Add focused tests for parsing, validation, edge cases, and output format.",
+ "Declare permissions honestly, especially network, clipboard, and file access.",
+ "Run pnpm test and pnpm build before opening the pull request.",
+ ].map((item) => (
+
Connect with us
-Join the Vite community
---
-
-
- GitHub
-
-
- -
-
-
- Discord
-
-
- -
-
-
- X.com
-
-
- -
-
-
- Bluesky
-
-
-
-