Files
gridline/www/src/layouts/Layout.astro
T
adrianbonpin f807c22a36 docs(www): point the website, docs and badges back at GitHub (#35)
* docs(www): point the website, docs and badges back at GitHub

GitHub is the release destination again, so every user-facing reference to the
self-hosted Gitea instance now points at github.com/AdrianBonpin:

- www: nav + footer links, Download / Releases buttons, 'View on GitHub' and
  'Star on GitHub' CTAs, landing.js download fallback label, the repo
  preconnect/dns-prefetch hints, and the ROADMAP link (Gitea's
  src/branch/prod path becomes GitHub's blob/prod)
- www/src/lib/seo.test.ts: matching structured-data fixtures
- README: shields.io badge and the tap prose
- ROADMAP: Homebrew tap URL

Deliberately left pointing at Gitea (they are the fallback, not destinations):
.gitea/workflows/release.yml and scripts/release-mac.sh use git.ranio.xyz as
an API host, so rewriting those would break the fallback path.

* fix(www): update the stale FAQ count assertion

faq.test.ts asserted exactly 5 FAQ items but the list has had 6 since
bdf2bb5 (Release 0.7.15), so the www suite has been red since then. No CI
runs the www tests, which is why it went unnoticed. Found while verifying the
docs change above; unrelated to it.
2026-09-20 17:43:15 +08:00

273 lines
9.3 KiB
Plaintext

---
import "../styles/global.css";
import Seo from "../components/Seo.astro";
import type { JsonLd } from "../lib/seo";
export interface Props {
title: string;
description?: string;
ogImage?: string;
noIndex?: boolean;
jsonLd?: JsonLd[];
}
const {
title,
description = "Gridline, the open-source database GUI for PostgreSQL, MySQL, SQLite, and Redis. No paywalls, no tab limits, fully open source.",
ogImage = "/og.png",
noIndex = false,
jsonLd = [],
} = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const ogImageURL = new URL(ogImage, Astro.site);
const isProd = import.meta.env.PROD;
---
<!doctype html>
<html lang="en" data-theme-auto class="anime-ready">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonicalURL} />
<link rel="sitemap" href="/sitemap-index.xml" />
<link rel="icon" type="image/svg+xml" href="/gridline-icon.svg" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="preload" as="image" href="/og.png" />
<link rel="preconnect" href="https://stat.ranio.xyz" crossorigin />
<link rel="preconnect" href="https://github.com" />
<link rel="dns-prefetch" href="https://stat.ranio.xyz" />
<link rel="dns-prefetch" href="https://github.com" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:url" content={canonicalURL} />
<meta property="og:image" content={ogImageURL} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="Gridline, the open-source database GUI" />
<meta property="og:site_name" content="Gridline" />
<meta property="og:locale" content="en_US" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={ogImageURL} />
<meta name="twitter:image:alt" content="Gridline, the open-source database GUI" />
<meta name="twitter:site" content="@AdrianBonpin" />
<meta name="twitter:creator" content="@AdrianBonpin" />
<meta name="theme-color" content="#0a0a0a" />
<meta name="color-scheme" content="dark light" />
{noIndex && <meta name="robots" content="noindex, nofollow" />}
<Seo jsonLd={jsonLd} />
{
// No-JS safety net: without JavaScript the anime.js entrance timeline
// never runs, so force every entry-animated element back to visible.
// (global.css hides them via the html.anime-ready selector.)
}
<noscript>
<style>
html.anime-ready .hero-animate > *,
html.anime-ready .hero-screenshot,
html.anime-ready .section-animate > * {
opacity: 1 !important;
}
</style>
</noscript>
{
isProd && (
<script
src="https://stat.ranio.xyz/api/script.js"
data-site-id="846d0ea9e3d0"
defer
/>
)
}
</head>
<body>
<nav id="nav" class="fixed top-0 inset-x-0 z-50 transition-all duration-300 border-b border-transparent">
<div class="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<a href="/" class="flex items-center gap-2.5 group">
<img src="/gridline-icon.svg" alt="Gridline" class="w-7 h-7" />
<span class="text-lg font-semibold tracking-tight">Gridline</span>
</a>
<div class="hidden md:flex items-center gap-6 text-sm">
<a href="/#features" class="nav-link">Features</a>
<a href="https://github.com/AdrianBonpin/gridline" target="_blank" rel="noopener" class="nav-link">GitHub</a>
<a href="/#download" data-download data-download-label class="nav-link">Download</a>
<button id="theme-toggle" class="p-2 rounded-full hover:bg-[var(--surface-elevated)] transition-colors" aria-label="Toggle theme">
<span class="theme-icon-sun hidden">☀</span>
<span class="theme-icon-moon">☾</span>
</button>
</div>
</div>
</nav>
<slot />
<footer class="border-t border-[var(--border)] mt-20 md:mt-28">
<div class="max-w-7xl mx-auto px-6 py-16 md:py-20">
<div class="grid md:grid-cols-4 gap-10 md:gap-8">
<div class="md:col-span-1">
<a href="/" class="flex items-center gap-2.5 mb-4">
<img src="/gridline-icon.svg" alt="Gridline" class="w-6 h-6" />
<span class="text-lg font-semibold">Gridline</span>
</a>
<p class="text-sm text-[var(--text-muted)] leading-relaxed">
The open-source database GUI that doesn't hold features hostage.
</p>
</div>
<div>
<h4 class="font-semibold text-sm mb-4">Product</h4>
<ul class="space-y-2 text-sm text-[var(--text-muted)]">
<li><a href="/#download" class="footer-link">Download</a></li>
<li><a href="/#features" class="footer-link">Features</a></li>
<li><a href="/changelog" class="footer-link">Changelog</a></li>
</ul>
</div>
<div>
<h4 class="font-semibold text-sm mb-4">Project</h4>
<ul class="space-y-2 text-sm text-[var(--text-muted)]">
<li><a href="https://github.com/AdrianBonpin/gridline" target="_blank" rel="noopener" class="footer-link">GitHub</a></li>
<li><a href="https://github.com/AdrianBonpin/gridline/issues" target="_blank" rel="noopener" class="footer-link">Issues</a></li>
<li><a href="https://github.com/AdrianBonpin/gridline/blob/prod/ROADMAP.md" target="_blank" rel="noopener" class="footer-link">Roadmap</a></li>
</ul>
</div>
<div>
<h4 class="font-semibold text-sm mb-4">Connect</h4>
<ul class="space-y-2 text-sm text-[var(--text-muted)]">
<li><a href="https://x.com/AdrianBonpin" target="_blank" rel="noopener" class="footer-link">X / Twitter</a></li>
<li><a href="mailto:adrianbonpin@gmail.com" class="footer-link">Email</a></li>
<li><a href="https://ko-fi.com/adrianbonpin" target="_blank" rel="noopener" class="footer-link">Support on Ko-fi ☕</a></li>
</ul>
</div>
</div>
<div class="mt-12 pt-8 border-t border-[var(--border)] flex flex-col md:flex-row justify-between gap-3 text-xs text-[var(--text-muted)] font-mono">
<span>© 2026 Gridline contributors · Apache 2.0</span>
<span>Coffee-powered.</span>
</div>
</div>
</footer>
</body>
</html>
<style is:global>
.nav-link {
position: relative;
color: var(--text-muted);
transition: color 0.2s;
}
.nav-link::after {
content: "";
position: absolute;
left: 0;
bottom: -2px;
width: 0;
height: 1px;
background: var(--primary);
transition: width 0.3s ease;
}
.nav-link:hover {
color: var(--text);
}
.nav-link:hover::after {
width: 100%;
}
.primary-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
border-radius: 9999px;
background: var(--primary);
color: white;
font-weight: 500;
transition: transform 0.2s, box-shadow 0.2s, background 0.2s;
}
.primary-btn:hover {
transform: scale(1.02);
background: var(--primary-hover);
box-shadow: 0 10px 30px -10px rgba(59, 130, 246, 0.4);
}
.ghost-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
border-radius: 9999px;
border: 1px solid var(--border);
color: var(--text);
font-weight: 500;
background: transparent;
transition: background 0.2s, border-color 0.2s;
}
.ghost-btn:hover {
background: var(--surface);
border-color: var(--surface-elevated);
}
.glass-card {
padding: 1.5rem;
border-radius: 1rem;
border: 1px solid var(--border);
background: var(--surface);
transition: background 0.2s, transform 0.2s;
}
.glass-card:hover {
background: var(--surface-elevated);
transform: translateY(-2px);
}
.feature-card {
padding: 1.25rem;
border-radius: 1rem;
border: 1px solid var(--border);
background: var(--surface);
}
.faq-item {
border: 1px solid var(--border);
border-radius: 0.75rem;
background: var(--surface);
overflow: hidden;
}
.faq-summary {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
cursor: pointer;
font-weight: 500;
list-style: none;
}
.faq-summary::-webkit-details-marker {
display: none;
}
.faq-chevron {
transition: transform 0.3s;
font-size: 1.25rem;
}
.faq-item[open] .faq-chevron {
transform: rotate(180deg);
}
.faq-body {
padding: 0 1.25rem 1.25rem;
}
.footer-link {
transition: color 0.2s, transform 0.2s;
display: inline-block;
}
.footer-link:hover {
color: var(--text);
transform: translateX(2px);
}
</style>