chore(branding): multi-modal press kit, hero OG + dashboard.gif (#323)
- Recolor banner/wordmark SVGs to the Otter Orange palette with brand fonts - Rebuild social-preview.png as a hero-style OG card (trust badges, headline, five modality cards with section counts), synced to apps/landing + apps/web og-image.png - Replace static dashboard PNGs with dashboard.gif: a guided tab tour of all 157 tools across the five modalities - Update branding/README.md (Otter Palette, brand fonts, asset list); add scripts/branding generator + sync helpers
@@ -18,7 +18,7 @@
|
|||||||
<a href="https://github.com/sponsors/snapotter-hq"><img src="https://img.shields.io/badge/Sponsor-pink?logo=githubsponsors&logoColor=white" alt="Sponsor"></a>
|
<a href="https://github.com/sponsors/snapotter-hq"><img src="https://img.shields.io/badge/Sponsor-pink?logo=githubsponsors&logoColor=white" alt="Sponsor"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 317 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 317 KiB |
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
Press kit for use in blog posts, articles, reviews, and other publications about SnapOtter. Free to use when referencing SnapOtter.
|
Press kit for use in blog posts, articles, reviews, and other publications about SnapOtter. Free to use when referencing SnapOtter.
|
||||||
|
|
||||||
|
SnapOtter is an open-source, self-hosted file processing suite with 157 tools across image, video, audio, PDF, and file workflows.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
| File | Size | Use |
|
| File | Size | Use |
|
||||||
@@ -17,23 +19,26 @@ Press kit for use in blog posts, articles, reviews, and other publications about
|
|||||||
| `social-preview.png` | 1280x640 | OpenGraph / social media preview |
|
| `social-preview.png` | 1280x640 | OpenGraph / social media preview |
|
||||||
| `wordmark.svg` | Scalable | "SnapOtter" text mark (dark text) |
|
| `wordmark.svg` | Scalable | "SnapOtter" text mark (dark text) |
|
||||||
| `wordmark-white.svg` | Scalable | "SnapOtter" text mark (white text, for dark backgrounds) |
|
| `wordmark-white.svg` | Scalable | "SnapOtter" text mark (white text, for dark backgrounds) |
|
||||||
| `banner.svg` | Scalable | Logo + tagline banner on dark background |
|
| `banner.svg` | Scalable | Wordmark + tagline banner on dark background |
|
||||||
| `dashboard.png` | Screenshot | Full dashboard screenshot for README / marketing |
|
| `dashboard.gif` | Animated | Scrolling tour of all 157 tools across the five modalities (README hero) |
|
||||||
| `dashboard_no_detail.png` | Screenshot | Dashboard screenshot without detail panel |
|
|
||||||
|
|
||||||
## Brand Colors
|
## Brand Colors
|
||||||
|
|
||||||
| Name | Hex | Usage |
|
| Name | Hex | Usage |
|
||||||
|------|-----|-------|
|
|------|-----|-------|
|
||||||
| Primary Blue | `#3b82f6` | "Otter" in wordmark, buttons, accents |
|
| Otter Orange | `#E07832` | "Otter" in wordmark, buttons, links, CTAs |
|
||||||
| Dark | `#0f172a` | "Snap" in wordmark, dark mode background |
|
| Orange Light | `#F09550` | Hover states |
|
||||||
| Slate | `#94a3b8` | Subtitle/muted text |
|
| Orange Dark | `#C06520` | Text emphasis on light backgrounds |
|
||||||
| White | `#ffffff` | Light mode background |
|
| Dark Chocolate | `#1A1210` | Dark sections, banner background |
|
||||||
|
| Foreground | `#1A1814` | "Snap" in wordmark, primary text |
|
||||||
|
| Cream | `#FAFAF7` | Page / light background |
|
||||||
|
| Muted | `#6B6560` | Secondary / subtitle text |
|
||||||
|
|
||||||
## Typography
|
## Typography
|
||||||
|
|
||||||
- **Headings / brand name**: Nunito (weight 800)
|
- **Headings / brand name**: Bricolage Grotesque (weight 800)
|
||||||
- **Body text**: Inter
|
- **Body text**: Instrument Sans
|
||||||
|
- **Code / monospace**: JetBrains Mono
|
||||||
|
|
||||||
## Usage Guidelines
|
## Usage Guidelines
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 720 200" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 720 200" fill="none">
|
||||||
<rect width="720" height="200" rx="16" fill="#0f172a"/>
|
<rect width="720" height="200" rx="16" fill="#1A1210"/>
|
||||||
<text x="220" y="105" font-family="Nunito, Inter, system-ui, sans-serif" font-size="64" font-weight="800" fill="#ffffff">
|
<text x="360" y="103" text-anchor="middle" font-family="'Bricolage Grotesque', 'Nunito', system-ui, sans-serif" font-size="64" font-weight="800" fill="#F0EBE4">
|
||||||
<tspan>Snap</tspan><tspan fill="#3b82f6">Otter</tspan>
|
<tspan>Snap</tspan><tspan fill="#E07832">Otter</tspan>
|
||||||
</text>
|
</text>
|
||||||
<text x="220" y="140" font-family="Inter, system-ui, sans-serif" font-size="18" fill="#94a3b8">
|
<text x="360" y="140" text-anchor="middle" font-family="'Instrument Sans', 'Inter', system-ui, sans-serif" font-size="18" letter-spacing="0.3" fill="#B0A79B">
|
||||||
Self-hosted image processing suite
|
Self-hosted file processing suite
|
||||||
</text>
|
</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 523 B After Width: | Height: | Size: 624 B |
|
After Width: | Height: | Size: 391 KiB |
|
Before Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 223 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 317 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 80" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 80" fill="none">
|
||||||
<text x="0" y="58" font-family="Nunito, Inter, system-ui, sans-serif" font-size="56" font-weight="800" fill="#ffffff">
|
<text x="0" y="58" font-family="'Bricolage Grotesque', 'Nunito', system-ui, sans-serif" font-size="56" font-weight="800" fill="#ffffff">
|
||||||
<tspan>Snap</tspan><tspan fill="#3b82f6">Otter</tspan>
|
<tspan>Snap</tspan><tspan fill="#E07832">Otter</tspan>
|
||||||
</text>
|
</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 271 B After Width: | Height: | Size: 289 B |
@@ -1,5 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 80" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 80" fill="none">
|
||||||
<text x="0" y="58" font-family="Nunito, Inter, system-ui, sans-serif" font-size="56" font-weight="800" fill="#0f172a">
|
<text x="0" y="58" font-family="'Bricolage Grotesque', 'Nunito', system-ui, sans-serif" font-size="56" font-weight="800" fill="#1A1814">
|
||||||
<tspan>Snap</tspan><tspan fill="#3b82f6">Otter</tspan>
|
<tspan>Snap</tspan><tspan fill="#E07832">Otter</tspan>
|
||||||
</text>
|
</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 271 B After Width: | Height: | Size: 289 B |
@@ -0,0 +1,150 @@
|
|||||||
|
// Generates branding/social-preview.png (1280x640) -- the SnapOtter social/OG card.
|
||||||
|
// Echoes the landing hero: trust badges, the "Every file tool you need / Nothing
|
||||||
|
// leaves your network" headline, the supporting subhead, and the five modality
|
||||||
|
// cards. Rendered from HTML with the real brand fonts via headless Chromium.
|
||||||
|
// Run: node scripts/branding/generate-social-preview.mjs
|
||||||
|
//
|
||||||
|
// The same output is the canonical OG image; sync it to apps/landing/public/og-image.png
|
||||||
|
// and apps/web/public/og-image.png (see scripts/branding/sync-og.sh).
|
||||||
|
|
||||||
|
import { rmSync, writeFileSync } from "node:fs";
|
||||||
|
import { dirname, resolve } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { chromium } from "@playwright/test";
|
||||||
|
|
||||||
|
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||||
|
const out = resolve(repoRoot, "branding/social-preview.png");
|
||||||
|
// Temp HTML lives in branding/ so the page origin is file:// and can load the
|
||||||
|
// sibling logo and the ../apps fonts as file:// subresources.
|
||||||
|
const htmlPath = resolve(repoRoot, "branding/.og.html");
|
||||||
|
|
||||||
|
// Enterprise trust badges (subset of Hero.astro's list).
|
||||||
|
const BADGES = ["On-premise", "GDPR & HIPAA", "Air-gap", "SAML SSO", "Open source"];
|
||||||
|
|
||||||
|
// lucide icon inner-SVG (stroke), matching the icons used in CategoryCards.astro.
|
||||||
|
const ICON = {
|
||||||
|
image:
|
||||||
|
'<rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>',
|
||||||
|
video:
|
||||||
|
'<path d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5"/><rect x="2" y="6" width="14" height="12" rx="2"/>',
|
||||||
|
music: '<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>',
|
||||||
|
fileText:
|
||||||
|
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/>',
|
||||||
|
database:
|
||||||
|
'<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5V19A9 3 0 0 0 21 19V5"/><path d="M3 12A9 3 0 0 0 21 12"/>',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mirrors CategoryCards.astro (label/blurb/color/icon). Counts are toolSection()
|
||||||
|
// section counts (image 64, video 29, audio 17, pdf 25, files 22 = 157); refresh
|
||||||
|
// with: tsx -e "import('@snapotter/shared')..." over toolSection (see git history).
|
||||||
|
const CARDS = [
|
||||||
|
{
|
||||||
|
label: "Image Tools",
|
||||||
|
count: 64,
|
||||||
|
blurb: "Resize, convert, and enhance",
|
||||||
|
color: "#E07832",
|
||||||
|
icon: ICON.image,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Video Tools",
|
||||||
|
count: 29,
|
||||||
|
blurb: "Trim, convert, and caption",
|
||||||
|
color: "#BE4A3C",
|
||||||
|
icon: ICON.video,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Audio Tools",
|
||||||
|
count: 17,
|
||||||
|
blurb: "Convert, trim, and transcribe",
|
||||||
|
color: "#2C7A75",
|
||||||
|
icon: ICON.music,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "PDF & Documents",
|
||||||
|
count: 25,
|
||||||
|
blurb: "Merge, split, and convert",
|
||||||
|
color: "#44568C",
|
||||||
|
icon: ICON.fileText,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "File Tools",
|
||||||
|
count: 22,
|
||||||
|
blurb: "Convert and transform",
|
||||||
|
color: "#5C8642",
|
||||||
|
icon: ICON.database,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const total = CARDS.reduce((s, c) => s + c.count, 0);
|
||||||
|
|
||||||
|
const badgesHtml = BADGES.map(
|
||||||
|
(b) =>
|
||||||
|
`<span class="badge"><svg class="chk" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>${b}</span>`,
|
||||||
|
).join("");
|
||||||
|
|
||||||
|
const cardsHtml = CARDS.map(
|
||||||
|
(c) => `<div class="card" style="background:${c.color}">
|
||||||
|
<div class="card-top">
|
||||||
|
<span class="card-icon"><svg viewBox="0 0 24 24">${c.icon}</svg></span>
|
||||||
|
<span class="card-pill">${c.count} tools</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-label">${c.label}</div>
|
||||||
|
<div class="card-blurb">${c.blurb}</div>
|
||||||
|
</div>`,
|
||||||
|
).join("");
|
||||||
|
|
||||||
|
const html = `<!doctype html><html><head><meta charset="utf-8"/><style>
|
||||||
|
@font-face{font-family:'Bricolage Grotesque';src:url('../apps/landing/public/fonts/bricolage-grotesque-var.woff2') format('woff2');font-weight:200 800;font-display:block}
|
||||||
|
@font-face{font-family:'Instrument Sans';src:url('../apps/landing/public/fonts/instrument-sans-400.woff2') format('woff2');font-weight:400;font-display:block}
|
||||||
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
html,body{width:1280px;height:640px}
|
||||||
|
.canvas{position:relative;width:1280px;height:640px;overflow:hidden;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 30px;
|
||||||
|
font-family:'Instrument Sans',system-ui,sans-serif;
|
||||||
|
background:
|
||||||
|
radial-gradient(60% 55% at 13% 8%, rgba(224,120,50,.10), transparent 62%),
|
||||||
|
radial-gradient(55% 55% at 88% 94%, rgba(68,86,140,.08), transparent 60%),
|
||||||
|
linear-gradient(158deg,#FFFFFF 0%,#FAFAF7 48%,#F3EEE6 100%)}
|
||||||
|
.brand{display:flex;align-items:center;gap:15px}
|
||||||
|
.brand img{width:56px;height:56px}
|
||||||
|
.brand .wm{font-family:'Bricolage Grotesque',system-ui,sans-serif;font-weight:800;font-size:46px;letter-spacing:-1px;color:#1A1814}
|
||||||
|
.brand .wm .o{color:#E07832}
|
||||||
|
.brand .pill{margin-left:4px;align-self:center;border-radius:999px;background:#FFF1E6;border:1px solid rgba(224,120,50,.30);color:#C06520;font-weight:700;font-size:16px;padding:6px 14px;letter-spacing:.2px}
|
||||||
|
.badges{display:flex;flex-wrap:wrap;justify-content:center;gap:9px;margin-top:20px}
|
||||||
|
.badge{display:inline-flex;align-items:center;gap:6px;border-radius:999px;border:1px solid rgba(224,120,50,.22);background:rgba(224,120,50,.06);padding:5px 13px;font-size:14px;font-weight:500;color:#C06520}
|
||||||
|
.badge .chk{width:13px;height:13px;stroke:#C06520;stroke-width:2.5;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
||||||
|
.headline{margin-top:22px;text-align:center;font-family:'Bricolage Grotesque',system-ui,sans-serif;font-weight:800;font-size:58px;line-height:1.04;letter-spacing:-1.4px;color:#1A1814}
|
||||||
|
.headline .f{color:#E07832}
|
||||||
|
.headline .hl2{display:block;margin-top:12px;font-size:32px;font-weight:700;letter-spacing:-.5px;
|
||||||
|
background:linear-gradient(90deg,#E07832,#F09550);-webkit-background-clip:text;background-clip:text;color:transparent}
|
||||||
|
.subhead{margin-top:15px;font-size:20px;color:#6B6560;text-align:center;max-width:760px}
|
||||||
|
.cards{display:flex;gap:16px;margin-top:30px}
|
||||||
|
.card{width:230px;border-radius:20px;padding:20px;color:#fff;display:flex;flex-direction:column;box-shadow:0 9px 22px rgba(26,18,16,.13)}
|
||||||
|
.card-top{display:flex;align-items:center;justify-content:space-between}
|
||||||
|
.card-icon{display:flex;align-items:center;justify-content:center;width:42px;height:42px;border-radius:11px;background:rgba(255,255,255,.20)}
|
||||||
|
.card-icon svg{width:23px;height:23px;stroke:#fff;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
||||||
|
.card-pill{background:rgba(255,255,255,.20);border-radius:999px;padding:3px 12px;font-size:13px;font-weight:600}
|
||||||
|
.card-label{font-family:'Bricolage Grotesque',system-ui,sans-serif;font-weight:700;font-size:20px;line-height:1.1;margin-top:34px}
|
||||||
|
.card-blurb{font-size:14px;color:rgba(255,255,255,.84);margin-top:6px}
|
||||||
|
</style></head><body><div class="canvas">
|
||||||
|
<div class="brand"><img src="logo-512.png" alt=""/><span class="wm">Snap<span class="o">Otter</span></span><span class="pill">${total} tools</span></div>
|
||||||
|
<div class="badges">${badgesHtml}</div>
|
||||||
|
<div class="headline">Every <span class="f">file</span> tool you need.<span class="hl2">Nothing leaves your network.</span></div>
|
||||||
|
<div class="subhead">The file-processing suite for teams that keep sensitive data in-house.</div>
|
||||||
|
<div class="cards">${cardsHtml}</div>
|
||||||
|
</div></body></html>`;
|
||||||
|
|
||||||
|
writeFileSync(htmlPath, html);
|
||||||
|
try {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const ctx = await browser.newContext({ viewport: { width: 1280, height: 640 } });
|
||||||
|
const page = await ctx.newPage();
|
||||||
|
await page.goto("file://" + htmlPath, { waitUntil: "load" });
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
await document.fonts.ready;
|
||||||
|
});
|
||||||
|
await page.waitForTimeout(150);
|
||||||
|
await page.screenshot({ path: out });
|
||||||
|
await browser.close();
|
||||||
|
console.log("wrote " + out);
|
||||||
|
} finally {
|
||||||
|
rmSync(htmlPath, { force: true });
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Propagate the canonical social card to the app OG-image locations.
|
||||||
|
# Run after regenerating branding/social-preview.png.
|
||||||
|
set -euo pipefail
|
||||||
|
root="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
src="$root/branding/social-preview.png"
|
||||||
|
cp "$src" "$root/apps/landing/public/og-image.png"
|
||||||
|
cp "$src" "$root/apps/web/public/og-image.png"
|
||||||
|
echo "synced social-preview.png -> apps/landing/public/og-image.png, apps/web/public/og-image.png"
|
||||||