mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: replace hardcoded "52 tools" with "50+" across all copy
Future-proofs marketing text so it doesn't need updating every time a tool is added.
This commit is contained in:
+2
-2
@@ -5,8 +5,8 @@ Live demo, per-tool landing pages, and a batch of polish fixes.
|
|||||||
## New features
|
## New features
|
||||||
|
|
||||||
- **Live demo** -- [demo.snapotter.com](https://demo.snapotter.com) lets people try SnapOtter without installing anything.
|
- **Live demo** -- [demo.snapotter.com](https://demo.snapotter.com) lets people try SnapOtter without installing anything.
|
||||||
- **Tools index page** -- Browse all 52 tools at `/tools` with search and category filters. Navbar links fixed on subpages.
|
- **Tools index page** -- Browse all 50+ tools at `/tools` with search and category filters. Navbar links fixed on subpages.
|
||||||
- **52 SEO landing pages** -- Every tool now has a dedicated landing page with FAQs, use cases, and comparison tables for search visibility.
|
- **50+ SEO landing pages** -- Every tool now has a dedicated landing page with FAQs, use cases, and comparison tables for search visibility.
|
||||||
- **Background preview** -- Before-after slider shows a checkered background behind transparent images so you can actually see what changed.
|
- **Background preview** -- Before-after slider shows a checkered background behind transparent images so you can actually see what changed.
|
||||||
- **Strong password generator** -- One-click button in the Add Members form generates a secure password.
|
- **Strong password generator** -- One-click button in the Add Members form generates a secure password.
|
||||||
- **Easter eggs** -- Hello from the otter side.
|
- **Easter eggs** -- Hello from the otter side.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
- **52 image tools:** Resize, crop, compress, convert, watermark, color adjust, beautify screenshots, generate memes, vectorize, create GIFs, find duplicates, generate passport photos, and more. Supports 55+ input formats (including 23 camera RAW formats) and 14 output formats
|
- **50+ image tools:** Resize, crop, compress, convert, watermark, color adjust, beautify screenshots, generate memes, vectorize, create GIFs, find duplicates, generate passport photos, and more. Supports 55+ input formats (including 23 camera RAW formats) and 14 output formats
|
||||||
- **Image editor:** Layer-based editor with brushes, shapes, adjustments, filters, curves, and keyboard shortcuts. Runs in your browser, processes on your hardware
|
- **Image editor:** Layer-based editor with brushes, shapes, adjustments, filters, curves, and keyboard shortcuts. Runs in your browser, processes on your hardware
|
||||||
- **Local AI:** Remove backgrounds, upscale images, restore and colorize old photos, erase objects, blur faces, enhance faces, extract text (OCR), expand canvas, fix transparency. All on your hardware, no internet required
|
- **Local AI:** Remove backgrounds, upscale images, restore and colorize old photos, erase objects, blur faces, enhance faces, extract text (OCR), expand canvas, fix transparency. All on your hardware, no internet required
|
||||||
- **OIDC / SSO:** Login with Google, GitHub, Okta, or any OpenID Connect provider
|
- **OIDC / SSO:** Login with Google, GitHub, Okta, or any OpenID Connect provider
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function generateLlmsTxt(spec: OpenAPISpec): string {
|
|||||||
lines.push(`# ${spec.info.title}`);
|
lines.push(`# ${spec.info.title}`);
|
||||||
lines.push("");
|
lines.push("");
|
||||||
lines.push(
|
lines.push(
|
||||||
"> Self-hosted image processing API with 52 tools. Resize, compress, convert, remove backgrounds, upscale, run OCR, and more.",
|
"> Self-hosted image processing API with 50+ tools. Resize, compress, convert, remove backgrounds, upscale, run OCR, and more.",
|
||||||
);
|
);
|
||||||
lines.push("");
|
lines.push("");
|
||||||
lines.push("## Docs");
|
lines.push("## Docs");
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ Live demo, per-tool landing pages, and a batch of polish fixes.
|
|||||||
### New features
|
### New features
|
||||||
|
|
||||||
- **Live demo** -- [demo.snapotter.com](https://demo.snapotter.com) lets people try SnapOtter without installing anything.
|
- **Live demo** -- [demo.snapotter.com](https://demo.snapotter.com) lets people try SnapOtter without installing anything.
|
||||||
- **Tools index page** -- Browse all 52 tools at `/tools` with search and category filters.
|
- **Tools index page** -- Browse all 50+ tools at `/tools` with search and category filters.
|
||||||
- **52 SEO landing pages** -- Every tool now has a dedicated landing page with FAQs, use cases, and comparison tables.
|
- **50+ SEO landing pages** -- Every tool now has a dedicated landing page with FAQs, use cases, and comparison tables.
|
||||||
- **Background preview** -- Before-after slider shows a checkered background behind transparent images.
|
- **Background preview** -- Before-after slider shows a checkered background behind transparent images.
|
||||||
- **Strong password generator** -- One-click button in the Add Members form.
|
- **Strong password generator** -- One-click button in the Add Members form.
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ Shared TypeScript types, constants (like `APP_VERSION` and tool definitions), an
|
|||||||
|
|
||||||
### API (`apps/api`)
|
### API (`apps/api`)
|
||||||
|
|
||||||
A Fastify v5 server exposing 52 tool routes (36 standard image operations + 16 AI-powered) that handles:
|
A Fastify v5 server exposing 50+ tool routes (36 standard image operations + 16 AI-powered) that handles:
|
||||||
- File uploads, temporary workspace management, and persistent file storage
|
- File uploads, temporary workspace management, and persistent file storage
|
||||||
- User file library with version chains (`user_files` table) -- each processed result links back to its source file and records which tool was applied, with auto-generated thumbnails for the Files page
|
- User file library with version chains (`user_files` table) -- each processed result links back to its source file and records which tool was applied, with auto-generated thumbnails for the Files page
|
||||||
- Tool execution (routes each tool request to the image engine or AI bridge)
|
- Tool execution (routes each tool request to the image engine or AI bridge)
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ layout: home
|
|||||||
hero:
|
hero:
|
||||||
name: "SnapOtter"
|
name: "SnapOtter"
|
||||||
text: "A Self Hosted Image Manipulator"
|
text: "A Self Hosted Image Manipulator"
|
||||||
tagline: 52 tools. Local AI. No cloud. Your images never leave your home.
|
tagline: 50+ tools. Local AI. No cloud. Your images never leave your home.
|
||||||
actions:
|
actions:
|
||||||
- theme: brand
|
- theme: brand
|
||||||
text: Get started
|
text: Get started
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const nunito = Nunito({ subsets: ["latin"], variable: "--font-nunito" });
|
|||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "SnapOtter | Self-Hosted Image Processing",
|
title: "SnapOtter | Self-Hosted Image Processing",
|
||||||
description:
|
description:
|
||||||
"52 image processing tools with local AI. Runs 100% offline. No data leaves your network. Open source and free forever.",
|
"50+ image processing tools with local AI. Runs 100% offline. No data leaves your network. Open source and free forever.",
|
||||||
metadataBase: new URL("https://snapotter.com"),
|
metadataBase: new URL("https://snapotter.com"),
|
||||||
icons: {
|
icons: {
|
||||||
icon: [
|
icon: [
|
||||||
@@ -24,7 +24,7 @@ export const metadata: Metadata = {
|
|||||||
openGraph: {
|
openGraph: {
|
||||||
title: "SnapOtter | Self-Hosted Image Processing",
|
title: "SnapOtter | Self-Hosted Image Processing",
|
||||||
description:
|
description:
|
||||||
"52 image processing tools with local AI. Runs 100% offline. No data leaves your network.",
|
"50+ image processing tools with local AI. Runs 100% offline. No data leaves your network.",
|
||||||
url: "https://snapotter.com",
|
url: "https://snapotter.com",
|
||||||
siteName: "SnapOtter",
|
siteName: "SnapOtter",
|
||||||
type: "website",
|
type: "website",
|
||||||
@@ -43,7 +43,7 @@ export const metadata: Metadata = {
|
|||||||
site: "@SnapOtterHQ",
|
site: "@SnapOtterHQ",
|
||||||
title: "SnapOtter | Self-Hosted Image Processing",
|
title: "SnapOtter | Self-Hosted Image Processing",
|
||||||
description:
|
description:
|
||||||
"52 image processing tools with local AI. Runs 100% offline. No data leaves your network.",
|
"50+ image processing tools with local AI. Runs 100% offline. No data leaves your network.",
|
||||||
images: ["/og-image.png"],
|
images: ["/og-image.png"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const websiteJsonLd = {
|
|||||||
name: "SnapOtter",
|
name: "SnapOtter",
|
||||||
url: "https://snapotter.com",
|
url: "https://snapotter.com",
|
||||||
description:
|
description:
|
||||||
"52 image processing tools with local AI. Runs 100% offline. No data leaves your network.",
|
"50+ image processing tools with local AI. Runs 100% offline. No data leaves your network.",
|
||||||
publisher: {
|
publisher: {
|
||||||
"@type": "Organization",
|
"@type": "Organization",
|
||||||
name: "SnapOtter",
|
name: "SnapOtter",
|
||||||
@@ -44,7 +44,7 @@ const softwareJsonLd = {
|
|||||||
"@type": "SoftwareApplication",
|
"@type": "SoftwareApplication",
|
||||||
name: "SnapOtter",
|
name: "SnapOtter",
|
||||||
description:
|
description:
|
||||||
"52 image processing tools with local AI. Runs 100% offline. No data leaves your network. Open source and free forever.",
|
"50+ image processing tools with local AI. Runs 100% offline. No data leaves your network. Open source and free forever.",
|
||||||
applicationCategory: "MultimediaApplication",
|
applicationCategory: "MultimediaApplication",
|
||||||
operatingSystem: "Linux, macOS, Windows (via Docker)",
|
operatingSystem: "Linux, macOS, Windows (via Docker)",
|
||||||
offers: {
|
offers: {
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ export default async function ToolPage({ params }: PageProps) {
|
|||||||
Ready to try {tool.name}?
|
Ready to try {tool.name}?
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mx-auto mt-4 max-w-lg text-muted">
|
<p className="mx-auto mt-4 max-w-lg text-muted">
|
||||||
Deploy SnapOtter in under a minute. All 52 tools included. Open source and free
|
Deploy SnapOtter in under a minute. All 50+ tools included. Open source and free
|
||||||
forever.
|
forever.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-8 flex flex-wrap justify-center gap-4">
|
<div className="mt-8 flex flex-wrap justify-center gap-4">
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function BentoGrid() {
|
|||||||
<div className="mx-auto max-w-6xl">
|
<div className="mx-auto max-w-6xl">
|
||||||
<FadeIn>
|
<FadeIn>
|
||||||
<h2 className="font-[family-name:var(--font-nunito)] text-center text-3xl font-bold tracking-tight md:text-4xl">
|
<h2 className="font-[family-name:var(--font-nunito)] text-center text-3xl font-bold tracking-tight md:text-4xl">
|
||||||
52 tools. Zero cloud dependency.
|
50+ tools. Zero cloud dependency.
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mx-auto mt-4 max-w-xl text-center text-lg text-muted">
|
<p className="mx-auto mt-4 max-w-xl text-center text-lg text-muted">
|
||||||
Search to find exactly what you need. Every tool runs 100% locally.
|
Search to find exactly what you need. Every tool runs 100% locally.
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export function Hero() {
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="mt-4 animate-[fadeUp_0.6s_ease-out_0.1s_both] text-base text-muted md:text-lg">
|
<p className="mt-4 animate-[fadeUp_0.6s_ease-out_0.1s_both] text-base text-muted md:text-lg">
|
||||||
A free, open-source image toolkit with 52 tools that runs entirely on your network.
|
A free, open-source image toolkit with 50+ tools that runs entirely on your network.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="mt-4 animate-[fadeUp_0.6s_ease-out_0.2s_both] text-xl font-medium md:text-2xl">
|
<p className="mt-4 animate-[fadeUp_0.6s_ease-out_0.2s_both] text-xl font-medium md:text-2xl">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const freePlan = {
|
|||||||
price: "Free",
|
price: "Free",
|
||||||
subtitle: "For everyone. Forever.",
|
subtitle: "For everyone. Forever.",
|
||||||
features: [
|
features: [
|
||||||
"All 52 image processing tools",
|
"All 50+ image processing tools",
|
||||||
"Unlimited usage, no hidden caps",
|
"Unlimited usage, no hidden caps",
|
||||||
"Full REST API with OpenAPI docs",
|
"Full REST API with OpenAPI docs",
|
||||||
"Pipeline automation",
|
"Pipeline automation",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const phrases = [
|
|||||||
"No limits. No hidden caps.",
|
"No limits. No hidden caps.",
|
||||||
"Works fully offline.",
|
"Works fully offline.",
|
||||||
"Unlimited batch processing.",
|
"Unlimited batch processing.",
|
||||||
"52 image tools.",
|
"50+ image tools.",
|
||||||
"16 AI models. Your hardware.",
|
"16 AI models. Your hardware.",
|
||||||
"Lightning fast. Built on Sharp.",
|
"Lightning fast. Built on Sharp.",
|
||||||
"Air-gapped ready.",
|
"Air-gapped ready.",
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "snapotter",
|
"name": "snapotter",
|
||||||
"version": "1.17.1",
|
"version": "1.17.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Self-hosted image processing suite with 52 tools and local AI. Single Docker container, no external services.",
|
"description": "Self-hosted image processing suite with 50+ tools and local AI. Single Docker container, no external services.",
|
||||||
"homepage": "https://snapotter.com",
|
"homepage": "https://snapotter.com",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -1611,7 +1611,7 @@ export const en = {
|
|||||||
heading: "About",
|
heading: "About",
|
||||||
appName: "SnapOtter",
|
appName: "SnapOtter",
|
||||||
appDescription:
|
appDescription:
|
||||||
"A self-hosted, privacy-first image processing suite with 52 tools. Resize, compress, convert, watermark, and automate your image workflows without sending data to the cloud.",
|
"A self-hosted, privacy-first image processing suite with 50+ tools. Resize, compress, convert, watermark, and automate your image workflows without sending data to the cloud.",
|
||||||
versionLabel: "Version:",
|
versionLabel: "Version:",
|
||||||
linksHeading: "Links",
|
linksHeading: "Links",
|
||||||
githubLink: "GitHub Repository",
|
githubLink: "GitHub Repository",
|
||||||
@@ -1654,7 +1654,7 @@ export const en = {
|
|||||||
"No limits. No hidden caps.",
|
"No limits. No hidden caps.",
|
||||||
"Works fully offline.",
|
"Works fully offline.",
|
||||||
"Unlimited batch processing.",
|
"Unlimited batch processing.",
|
||||||
"52 image tools.",
|
"50+ image tools.",
|
||||||
"16 AI models. Your hardware.",
|
"16 AI models. Your hardware.",
|
||||||
"Lightning fast. Built on Sharp.",
|
"Lightning fast. Built on Sharp.",
|
||||||
"Air-gapped ready.",
|
"Air-gapped ready.",
|
||||||
|
|||||||
@@ -1667,7 +1667,7 @@ export const nl: TranslationKeys = {
|
|||||||
heading: "Over",
|
heading: "Over",
|
||||||
appName: "SnapOtter",
|
appName: "SnapOtter",
|
||||||
appDescription:
|
appDescription:
|
||||||
"Een zelf-gehoste, privacy-first beeldverwerkingssuite met 52 tools. Schalen, comprimeren, converteren, watermerken en je beeldworkflows automatiseren zonder data naar de cloud te sturen.",
|
"Een zelf-gehoste, privacy-first beeldverwerkingssuite met 50+ tools. Schalen, comprimeren, converteren, watermerken en je beeldworkflows automatiseren zonder data naar de cloud te sturen.",
|
||||||
versionLabel: "Versie:",
|
versionLabel: "Versie:",
|
||||||
linksHeading: "Links",
|
linksHeading: "Links",
|
||||||
githubLink: "GitHub-repository",
|
githubLink: "GitHub-repository",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ test.describe("Docs Homepage", () => {
|
|||||||
await expect(page.getByText("SnapOtter").first()).toBeVisible();
|
await expect(page.getByText("SnapOtter").first()).toBeVisible();
|
||||||
await expect(page.getByText("A Self Hosted Image Manipulator")).toBeVisible();
|
await expect(page.getByText("A Self Hosted Image Manipulator")).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText("52 tools. Local AI. No cloud. Your images never leave your home."),
|
page.getByText("50+ tools. Local AI. No cloud. Your images never leave your home."),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ test.describe("Landing Homepage", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("bento grid renders with search and tool count", async ({ page }) => {
|
test("bento grid renders with search and tool count", async ({ page }) => {
|
||||||
await expect(page.getByText("52 tools. Zero cloud dependency.")).toBeVisible();
|
await expect(page.getByText("50+ tools. Zero cloud dependency.")).toBeVisible();
|
||||||
await expect(page.getByPlaceholder("Search tools...")).toBeVisible();
|
await expect(page.getByPlaceholder("Search tools...")).toBeVisible();
|
||||||
await expect(page.getByText(/Showing 52 of 52 tools/)).toBeVisible();
|
await expect(page.getByText(/Showing 52 of 52 tools/)).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ function generateLlmsTxt(spec: OpenAPISpec): string {
|
|||||||
lines.push(`# ${spec.info.title}`);
|
lines.push(`# ${spec.info.title}`);
|
||||||
lines.push("");
|
lines.push("");
|
||||||
lines.push(
|
lines.push(
|
||||||
"> Self-hosted image processing API with 52 tools. Resize, compress, convert, remove backgrounds, upscale, run OCR, and more.",
|
"> Self-hosted image processing API with 50+ tools. Resize, compress, convert, remove backgrounds, upscale, run OCR, and more.",
|
||||||
);
|
);
|
||||||
lines.push("");
|
lines.push("");
|
||||||
lines.push("## Docs");
|
lines.push("## Docs");
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function getCountText(): string {
|
|||||||
describe("BentoGrid", () => {
|
describe("BentoGrid", () => {
|
||||||
it("renders the section heading", () => {
|
it("renders the section heading", () => {
|
||||||
render(<BentoGrid />);
|
render(<BentoGrid />);
|
||||||
expect(screen.getByText("52 tools. Zero cloud dependency.")).toBeDefined();
|
expect(screen.getByText("50+ tools. Zero cloud dependency.")).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders the search input", () => {
|
it("renders the search input", () => {
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ describe("Pricing", () => {
|
|||||||
|
|
||||||
it("renders free plan features", () => {
|
it("renders free plan features", () => {
|
||||||
render(<Pricing />);
|
render(<Pricing />);
|
||||||
expect(screen.getByText("All 52 image processing tools")).toBeDefined();
|
expect(screen.getByText("All 50+ image processing tools")).toBeDefined();
|
||||||
expect(screen.getByText("Unlimited usage, no hidden caps")).toBeDefined();
|
expect(screen.getByText("Unlimited usage, no hidden caps")).toBeDefined();
|
||||||
expect(screen.getByText("15 local AI models included")).toBeDefined();
|
expect(screen.getByText("15 local AI models included")).toBeDefined();
|
||||||
expect(screen.getByText("AGPL-3.0 licensed")).toBeDefined();
|
expect(screen.getByText("AGPL-3.0 licensed")).toBeDefined();
|
||||||
|
|||||||
Reference in New Issue
Block a user