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:
SnapOtter
2026-05-20 14:45:45 +08:00
parent dfbc4cfd59
commit e70fca054a
21 changed files with 27 additions and 27 deletions
+2 -2
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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");
+2 -2
View File
@@ -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.
+1 -1
View File
@@ -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
View File
@@ -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
+3 -3
View File
@@ -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"],
}, },
}; };
+2 -2
View File
@@ -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: {
+1 -1
View File
@@ -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">
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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">
+1 -1
View File
@@ -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
View File
@@ -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",
+2 -2
View File
@@ -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.",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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();
}); });
+1 -1
View File
@@ -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();
}); });
+1 -1
View File
@@ -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");
+1 -1
View File
@@ -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", () => {
+1 -1
View File
@@ -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();