Files
iptvnator/apps/website/src/lib/feature-schema.ts
T
4grayandClaude Fable 5.1 ad81fbfc45 feat(website): add the features hub and five feature landing pages
Publish /features/ with one landing page per feature people search for:
M3U playlist player, Xtream Codes player, Stalker portal player, TV
guide (EPG) and phone remote control. Each page composes a feature hero
(download and setup-guide calls to action) with the download-page
sections, carries SoftwareApplication (featureList) / FAQPage /
BreadcrumbList structured data, links to the other feature pages and
the matching guides, and uses only mock-backed screenshots.

A registry in src/lib/features.ts drives the hub, the per-page
switcher, the homepage feature cards (now links) and the header
Features entry, so a new page is one registry entry and one .astro
file. tools/testing/website-feature-pages.test.mjs checks canonical
URLs, schema, cross-links, hub coverage and sitemap entries.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 17:09:03 +02:00

58 lines
2.0 KiB
TypeScript

import type { FaqEntry } from './download-schema';
import { REPO_URL } from './downloads';
import type { FeatureEntry } from './features';
import { FEATURES_HUB_HREF } from './features';
import { absoluteUrl } from './site';
interface FeaturePageSchemaInput {
feature: FeatureEntry;
pageUrl: string;
description: string;
/** Plain-language feature statements for schema.org `featureList`. */
featureList: string[];
faq: FaqEntry[];
}
/** SoftwareApplication (with featureList) + FAQPage + BreadcrumbList for one feature page. */
export function buildFeaturePageSchema(input: FeaturePageSchemaInput): Array<Record<string, unknown>> {
const { feature, pageUrl, description, featureList, faq } = input;
return [
{
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'IPTVnator',
applicationCategory: 'MultimediaApplication',
applicationSubCategory: 'IPTV player',
operatingSystem: 'Windows, macOS, Linux',
url: pageUrl,
downloadUrl: absoluteUrl('/download/'),
featureList,
isAccessibleForFree: true,
license: `${REPO_URL}/blob/master/LICENSE`,
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
author: { '@type': 'Person', name: '4gray', url: 'https://github.com/4gray' },
sameAs: [REPO_URL, 'https://t.me/iptvnator'],
description,
},
{
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: faq.map((entry) => ({
'@type': 'Question',
name: entry.q,
acceptedAnswer: { '@type': 'Answer', text: entry.a },
})),
},
{
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: 'IPTVnator', item: absoluteUrl('/') },
{ '@type': 'ListItem', position: 2, name: 'Features', item: absoluteUrl(FEATURES_HUB_HREF.replace('/iptvnator', '')) },
{ '@type': 'ListItem', position: 3, name: feature.label, item: pageUrl },
],
},
];
}