mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Harden Docker runtime packaging, preserve async job response semantics, fix Redis subscriber startup connections, clear lint warnings, and harden enterprise S3 object body handling.
159 lines
6.6 KiB
Plaintext
159 lines
6.6 KiB
Plaintext
---
|
|
// biome-ignore-all lint/correctness/noUnusedVariables: Astro template consumes frontmatter values.
|
|
import { formatCompact, getStarCount } from "@/lib/stats";
|
|
|
|
// Fetched at build time; refreshed by the scheduled landing rebuild.
|
|
const starCount = formatCompact(await getStarCount());
|
|
|
|
const links = [
|
|
{ label: "Features", href: "/#features" },
|
|
{ label: "Enterprise", href: "/#enterprise" },
|
|
{ label: "Pricing", href: "/#pricing" },
|
|
{ label: "Alternatives", href: "/alternatives" },
|
|
{ label: "Docs", href: "https://docs.snapotter.com", external: true },
|
|
{ label: "Contact", href: "/contact" },
|
|
];
|
|
---
|
|
|
|
<nav class="fixed top-0 left-0 right-0 z-50 bg-background/70 backdrop-blur-xl after:absolute after:bottom-0 after:left-0 after:h-px after:w-full after:bg-gradient-to-r after:from-transparent after:via-primary/20 after:to-transparent">
|
|
<div class="mx-auto flex h-16 max-w-6xl items-center justify-between px-6">
|
|
<!-- Logo -->
|
|
<a href="/" class="flex items-center gap-2.5">
|
|
<img src="/logo.png" alt="SnapOtter" class="h-8 w-8" width="32" height="32" />
|
|
<span class="font-display text-lg font-bold tracking-tight">SnapOtter</span>
|
|
</a>
|
|
|
|
<!-- Desktop nav links -->
|
|
<div class="hidden items-center gap-7 lg:flex">
|
|
{links.map((item) => (
|
|
<a
|
|
href={item.href}
|
|
class="text-sm font-medium text-muted transition-colors hover:text-foreground"
|
|
{...(item.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
|
|
>
|
|
{item.label}
|
|
</a>
|
|
))}
|
|
</div>
|
|
|
|
<!-- Desktop CTAs -->
|
|
<div class="hidden items-center gap-3 md:flex">
|
|
<a
|
|
href="https://github.com/snapotter-hq/snapotter"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="flex items-center gap-1.5 rounded-lg border border-border px-3 py-1.5 text-sm font-medium transition-colors hover:bg-background-alt"
|
|
>
|
|
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
|
<svg class="h-3.5 w-3.5 text-primary" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
|
|
<span id="gh-stars" class="text-xs">{starCount}</span>
|
|
</a>
|
|
<a
|
|
href="https://demo.snapotter.com"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="rounded-lg border border-border px-4 py-1.5 text-sm font-medium transition-colors hover:bg-background-alt"
|
|
>
|
|
Try Demo
|
|
</a>
|
|
<a
|
|
href="/contact"
|
|
class="btn-primary rounded-lg px-4 py-1.5 text-sm font-semibold"
|
|
>
|
|
Book a Demo
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Mobile menu toggle (CSS-only) -->
|
|
<label for="mobile-menu-toggle" class="cursor-pointer text-muted lg:hidden" aria-label="Toggle menu">
|
|
<svg class="menu-open h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
|
<svg class="menu-close hidden h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Hidden checkbox for CSS toggle -->
|
|
<input type="checkbox" id="mobile-menu-toggle" class="peer hidden" aria-hidden="true" />
|
|
|
|
<!-- Mobile menu panel -->
|
|
<div class="hidden border-t border-border bg-surface px-6 py-4 peer-checked:block lg:hidden">
|
|
{links.map((item) => (
|
|
<a
|
|
href={item.href}
|
|
class="block py-2.5 text-sm font-medium text-muted transition-colors hover:text-foreground"
|
|
{...(item.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
|
|
>
|
|
{item.label}
|
|
</a>
|
|
))}
|
|
<div class="mt-3 flex flex-col gap-2">
|
|
<a
|
|
href="https://demo.snapotter.com"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="block rounded-lg border border-border px-4 py-2 text-center text-sm font-medium"
|
|
>
|
|
Try Demo
|
|
</a>
|
|
<a
|
|
href="/contact"
|
|
class="btn-primary block rounded-lg px-4 py-2 text-center text-sm font-semibold"
|
|
>
|
|
Book a Demo
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<style>
|
|
#mobile-menu-toggle:checked ~ nav .menu-open,
|
|
.menu-close {
|
|
display: none;
|
|
}
|
|
#mobile-menu-toggle:checked ~ nav .menu-close {
|
|
display: block;
|
|
}
|
|
/* Fix: target the label's SVGs via the peer */
|
|
:global(#mobile-menu-toggle:checked + div .menu-open) {
|
|
display: none;
|
|
}
|
|
:global(#mobile-menu-toggle:checked + div .menu-close) {
|
|
display: block;
|
|
}
|
|
</style>
|
|
|
|
<script is:inline>
|
|
// Live GitHub star count, cached in localStorage for 1 hour so a visitor browsing
|
|
// many pages only hits the API once. Falls back to the build-time value on failure.
|
|
(function () {
|
|
var el = document.getElementById("gh-stars");
|
|
if (!el) return;
|
|
var KEY = "snapotter:gh-stars";
|
|
var TTL = 3600000; // 1 hour
|
|
function fmt(n) {
|
|
return n >= 1000
|
|
? ((n / 1000) % 1 === 0 ? (n / 1000).toFixed(0) : (n / 1000).toFixed(1)) + "k"
|
|
: String(n);
|
|
}
|
|
try {
|
|
var cached = JSON.parse(localStorage.getItem(KEY) || "null");
|
|
if (cached && typeof cached.n === "number" && Date.now() - cached.t < TTL) {
|
|
el.textContent = fmt(cached.n);
|
|
return;
|
|
}
|
|
} catch (e) {}
|
|
fetch("https://api.github.com/repos/snapotter-hq/snapotter")
|
|
.then(function (r) {
|
|
return r.ok ? r.json() : null;
|
|
})
|
|
.then(function (d) {
|
|
if (d && typeof d.stargazers_count === "number") {
|
|
el.textContent = fmt(d.stargazers_count);
|
|
try {
|
|
localStorage.setItem(KEY, JSON.stringify({ n: d.stargazers_count, t: Date.now() }));
|
|
} catch (e) {}
|
|
}
|
|
})
|
|
.catch(function () {});
|
|
})();
|
|
</script>
|