diff --git a/e2e/seo.spec.ts b/e2e/seo.spec.ts new file mode 100644 index 0000000..ef6ae58 --- /dev/null +++ b/e2e/seo.spec.ts @@ -0,0 +1,72 @@ +import { test, expect } from '@playwright/test' + +// Discoverability kit (Flywheel Standard §5c, docs/standards/discoverability.md): +// what search crawlers, link unfurlers and AI agents read. Every file is asserted +// by content-type too — an SPA fallback answers 200 text/html for anything missing. + +test('landing page has honest title, description and canonical', async ({ page }) => { + await page.goto('/') + await expect(page).toHaveTitle(/Kam na mši/) + await expect(page.locator('link[rel="canonical"]')).toHaveAttribute( + 'href', + 'https://bohosluzby.dravec.org/', + ) + await expect(page.locator('meta[name="description"]')).toHaveAttribute('content', /bohoslužby/) +}) + +test('sitemap.xml and robots.txt are served', async ({ request }) => { + const sitemap = await request.get('/sitemap.xml') + expect(sitemap.ok()).toBe(true) + expect(sitemap.headers()['content-type']).toMatch(/xml/) + const xml = await sitemap.text() + expect(xml).toContain('https://bohosluzby.dravec.org/') + expect(xml).toContain('https://bohosluzby.dravec.org/mesto/') + + const robots = await request.get('/robots.txt') + expect(robots.ok()).toBe(true) + expect(robots.headers()['content-type']).toMatch(/^text\/plain/) + const txt = await robots.text() + expect(txt).toMatch(/^User-agent: \*\nAllow: \/$/m) // everyone welcome + expect(txt).toContain('User-agent: ClaudeBot') // AI agents named, not left to silence + expect(txt).not.toMatch(/^Disallow: \/\s*$/m) + expect(txt).toContain('Sitemap: https://bohosluzby.dravec.org/sitemap.xml') +}) + +test('llms.txt, security.txt and the 1200×630 share card are served', async ({ request }) => { + const llms = await request.get('/llms.txt') + expect(llms.ok()).toBe(true) + expect(llms.headers()['content-type']).toMatch(/^text\/plain/) + const body = await llms.text() + expect(body.startsWith('# Kam na mši')).toBe(true) + expect(body).toContain('https://bohosluzby.dravec.org/sitemap.xml') + + const sec = await request.get('/.well-known/security.txt') + expect(sec.ok()).toBe(true) + expect(await sec.text()).toMatch(/^Contact: https:\/\//m) + + const og = await request.get('/og.png') + expect(og.ok()).toBe(true) + expect(og.headers()['content-type']).toBe('image/png') + const png = await og.body() + // IHDR width/height live at bytes 16..24 of a PNG + expect(png.readUInt32BE(16)).toBe(1200) + expect(png.readUInt32BE(20)).toBe(630) +}) + +test('head carries the share-card block and WebApplication JSON-LD', async ({ page }) => { + await page.goto('/') + await expect(page.locator('meta[property="og:image"]')).toHaveAttribute( + 'content', + 'https://bohosluzby.dravec.org/og.png', + ) + await expect(page.locator('meta[property="og:image:width"]')).toHaveAttribute('content', '1200') + await expect(page.locator('meta[name="twitter:card"]')).toHaveAttribute('content', 'summary_large_image') + await expect(page.locator('meta[name="twitter:image"]')).toHaveAttribute( + 'content', + 'https://bohosluzby.dravec.org/og.png', + ) + const ld = JSON.parse((await page.locator('script[type="application/ld+json"]').first().textContent()) ?? '{}') + expect(ld['@type']).toBe('WebApplication') + expect(ld.url).toBe('https://bohosluzby.dravec.org/') + expect(ld.isAccessibleForFree).toBe(true) +}) diff --git a/index.html b/index.html index 0d0d6e1..38a7424 100644 --- a/index.html +++ b/index.html @@ -21,13 +21,33 @@ property="og:description" content="Nejbližší katolické bohoslužby v ČR podle vaší polohy — kterou mši svatou ještě stihnete. Zdarma, bez reklam a bez registrace." /> - - + + + + + + + + diff --git a/public/.well-known/security.txt b/public/.well-known/security.txt new file mode 100644 index 0000000..feb4dbd --- /dev/null +++ b/public/.well-known/security.txt @@ -0,0 +1,4 @@ +Contact: https://github.com/Chartres/bohosluzby/issues +Expires: 2027-09-01T00:00:00.000Z +Preferred-Languages: cs, en +Canonical: https://bohosluzby.dravec.org/.well-known/security.txt diff --git a/public/llms.txt b/public/llms.txt new file mode 100644 index 0000000..db438ec --- /dev/null +++ b/public/llms.txt @@ -0,0 +1,40 @@ +# Kam na mši + +> Mše svatá poblíž, právě teď. Webová a iOS aplikace, která najde nejbližší katolické +> bohoslužby v České republice podle vaší polohy a seřadí je podle toho, kterou ještě +> stihnete — hned, dnes, zítra nebo v neděli. Pořady bohoslužeb pro zhruba 4 000 kostelů +> a kaplí z oficiálního rejstříku České biskupské konference. Zdarma, bez reklam, bez +> registrace, bez cookies; funguje offline. Česky, s anglickým rozhraním. + +## What it is +- A mass finder for Czechia: "which Catholic mass can I still make, when and where". +- Day switcher (now / today / tomorrow / Sunday) works like leafing through the ordo; a map + with the next mass on each pin (greyed when it is on another day than the one you picked). +- Church detail: weekly schedule, extraordinary services, parish contacts, map / navigation + link (mapy.cz), shareable URL per church (`/kostel//`), add-to-calendar (ICS). +- Filters: only Holy Mass, wheelchair access, Greek-Catholic rite, language. +- Offline: the whole dataset (~1.3 MB) is stored on the device; the last location is + remembered. The iOS app (App Store, "Kam na mši") adds reminders and native sharing. +- Free, no account, no ads, no cookies; only first-party anonymous usage counts. + +## How to use it +- https://bohosluzby.dravec.org/ — allow location (or pick a city) and read the list. +- City pages are real URLs, e.g. https://bohosluzby.dravec.org/mesto/praha/ (see the sitemap). +- Church pages: https://bohosluzby.dravec.org/kostel// + +## Data & provenance +- Source: the official register of the Czech Bishops' Conference, https://bohosluzby.cirkev.cz + — parishes maintain their own entries. The app refreshes the dataset monthly and at the + start of each liturgical season, and shows "naposledy ověřeno" (last verified) per church. +- Some register entries are old; the app flags stale ones. Before travelling, confirm the time + with the parish. + +## Links +- App: https://bohosluzby.dravec.org/ +- iOS: search "Kam na mši" on the App Store +- Source code: https://github.com/Chartres/bohosluzby +- Sitemap: https://bohosluzby.dravec.org/sitemap.xml +- More from the author: https://flywheel.dravec.org/ + +## Contact +- Issues: https://github.com/Chartres/bohosluzby/issues diff --git a/public/og.png b/public/og.png new file mode 100644 index 0000000..29ec6df Binary files /dev/null and b/public/og.png differ diff --git a/public/robots.txt b/public/robots.txt index 6a899d6..98f8723 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -1,4 +1,16 @@ +# Everyone is welcome — search engines, link unfurlers and AI agents alike. User-agent: * Allow: / +# Named so nobody mistakes silence for a block (same rule as above). +User-agent: GPTBot +User-agent: ClaudeBot +User-agent: Claude-User +User-agent: Claude-SearchBot +User-agent: PerplexityBot +User-agent: Google-Extended +User-agent: Applebot-Extended +User-agent: CCBot +Allow: / + Sitemap: https://bohosluzby.dravec.org/sitemap.xml