Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions e2e/seo.spec.ts
Original file line number Diff line number Diff line change
@@ -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('<loc>https://bohosluzby.dravec.org/</loc>')
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)
})
24 changes: 22 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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."
/>
<meta property="og:image" content="https://bohosluzby.dravec.org/icons/icon-512.png" />
<meta name="twitter:card" content="summary" />
<!-- Share card: 1200×630 public/og.png, generated by flywheel scripts/og-image.mjs
(Standard §5c) — never the square app icon, unfurlers crop it. -->
<meta property="og:image" content="https://bohosluzby.dravec.org/og.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="Kam na mši — mše svatá poblíž, právě teď" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Kam na mši — mše svatá poblíž, právě teď" />
<meta
name="twitter:description"
content="Nejbližší katolické bohoslužby v ČR podle vaší polohy — kterou mši svatou ještě stihnete. Zdarma, bez reklam a bez registrace."
/>
<meta name="twitter:image" content="https://bohosluzby.dravec.org/og.png" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "Kam na mši",
"url": "https://bohosluzby.dravec.org/",
"description": "Nejbližší katolické bohoslužby v ČR podle vaší polohy — kterou mši svatou ještě stihnete, kdy a kde. Oficiální data bohosluzby.cirkev.cz. Zdarma, bez reklam a bez registrace.",
"applicationCategory": "UtilitiesApplication",
"operatingSystem": "Any",
"inLanguage": "cs",
"isAccessibleForFree": true,
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "CZK" }
}
</script>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
<meta name="theme-color" content="#f6f1e5" />
Expand Down
4 changes: 4 additions & 0 deletions public/.well-known/security.txt
Original file line number Diff line number Diff line change
@@ -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
40 changes: 40 additions & 0 deletions public/llms.txt
Original file line number Diff line number Diff line change
@@ -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/<id>/`), 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/<id>/

## 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
Binary file added public/og.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions public/robots.txt
Original file line number Diff line number Diff line change
@@ -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
Loading