feat: add TabFM extension page + pending website updates

- Add TabFM extension page (tabfm.md) with 15 soundscapes, FAQ, privacy section
- Update content schema, Base layout, slug page, styles (pending changes)
- Deploy to greasysprocket.com via rsync
This commit is contained in:
Jose Juan Moñino
2026-07-10 17:33:32 +02:00
parent cb357f2103
commit 0bc8ac75cd
8 changed files with 475 additions and 18 deletions
+134 -15
View File
@@ -1,6 +1,6 @@
---
import Base from '../layouts/Base.astro';
import { getCollection } from 'astro:content';
import { getCollection, type CollectionEntry } from 'astro:content';
export async function getStaticPaths() {
const extensions = await getCollection('extensions');
@@ -11,10 +11,85 @@ export async function getStaticPaths() {
}
const { ext } = Astro.props;
const { Content } = await ext.render();
const { Content, headings } = await ext.render();
// Build table of contents from h2 headings only
const tocItems = headings
.filter((h: any) => h.depth === 2)
.map((h: any) => ({
text: h.text,
slug: h.slug,
}));
const siteUrl = 'https://greasysprocket.com';
const canonicalUrl = `${siteUrl}/${ext.slug}`;
const ogImage = ext.data.screenshot
? new URL(ext.data.screenshot, siteUrl).href
: undefined;
// JSON-LD: SoftwareApplication
const softwareAppLd = {
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: ext.data.name,
description: ext.data.description,
applicationCategory: 'BrowserExtension',
operatingSystem: 'Chrome',
url: canonicalUrl,
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
},
...(ext.data.chromeStoreUrl && { installUrl: ext.data.chromeStoreUrl }),
};
// JSON-LD: BreadcrumbList
const breadcrumbLd = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{
'@type': 'ListItem',
position: 1,
name: 'Extensions',
item: siteUrl,
},
{
'@type': 'ListItem',
position: 2,
name: ext.data.name,
item: canonicalUrl,
},
],
};
// JSON-LD: FAQPage (if FAQs exist)
const faqLd = ext.data.faq
? {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: ext.data.faq.map((f: any) => ({
'@type': 'Question',
name: f.question,
acceptedAnswer: {
'@type': 'Answer',
text: f.answer,
},
})),
}
: null;
const jsonLd = [softwareAppLd, breadcrumbLd, ...(faqLd ? [faqLd] : [])];
---
<Base title={`${ext.data.name} — Greasy Sprocket`} description={ext.data.tagline}>
<Base
title={`${ext.data.name} — Free Chrome Extension | Greasy Sprocket`}
description={ext.data.description}
canonical={canonicalUrl}
ogImage={ogImage}
jsonLd={jsonLd}
>
<section class="ext-hero">
<div class="container">
<div class="info">
@@ -43,19 +118,63 @@ const { Content } = await ext.render();
</section>
<section class="section">
<div class="container">
<div class="section-label">Features</div>
<div class="feature-list">
{ext.data.features?.map((feature: string) => (
<div class="feature-item">
<span class="check">✓</span>
<h4>{feature}</h4>
</div>
))}
</div>
<div class="prose">
<Content />
<div class="container ext-layout">
{tocItems.length > 0 && (
<aside class="toc">
<div class="section-label">Contents</div>
<nav>
<ol>
{tocItems.map((item: any) => (
<li>
<a href={`#${item.slug}`}>{item.text}</a>
</li>
))}
{ext.data.faq && ext.data.faq.length > 0 && (
<li>
<a href="#faq">FAQ</a>
</li>
)}
</ol>
</nav>
</aside>
)}
<div class="ext-content">
<div class="section-label">Features</div>
<div class="feature-list">
{ext.data.features?.map((feature: string) => (
<div class="feature-item">
<span class="check">✓</span>
<h4>{feature}</h4>
</div>
))}
</div>
<div class="prose">
<Content />
</div>
</div>
</div>
</section>
{ext.data.faq && ext.data.faq.length > 0 && (
<section class="section" id="faq">
<div class="container faq-container">
<div class="section-label">FAQ</div>
<h2>Frequently asked questions</h2>
<div class="faq-list">
{ext.data.faq.map((f: any, i: number) => (
<details class="faq-item" open={i === 0}>
<summary>
<span>{f.question}</span>
<span class="faq-toggle" aria-hidden="true"></span>
</summary>
<div class="faq-answer">
<p>{f.answer}</p>
</div>
</details>
))}
</div>
</div>
</section>
)}
</Base>