ClientProduction2026

e-support Private KEP, Ioannina

Lead Developer

Bilingual website for a private citizen-services bureau in Ioannina: 12 service categories, JSON-LD with GEMI/VAT identifiers, an AEO layer with build-time llms.txt and a markdown mirror, and CI gates at every step. From design comps to Cloudflare production.

Results

service categories on one page
12
step-by-step guides
4
languages, with every page mirrored
2
e-support Private KEP, Ioannina

Stack

  • Astro 7
  • Tailwind v4
  • Cloudflare Workers
  • TypeScript
  • Playwright + vitest
  • llms.txt / AEO
  • JSON-LD
  • rorsa-platform forms

The situation

e-support is a private citizen-services bureau in Ioannina. It handles the cases that go through the state’s digital portals on the citizen’s behalf, in person in the city and remotely across the country. It needed a site that explains clearly what it takes on, states outright that it is not a public authority, and stands up in English too. I delivered it as the sole developer, from the design comps to the DNS.

A visitor can see at once whether their case is covered

The e-support services page: a dark green header and menu above the services heading, and a photograph of a service desk stacked with folders
The services page: the twelve categories start below the photograph

People do not search for “private citizen-services bureau”, they search for “pension”, “disability certification”, “benefit”. The twelve categories are written in the words the visitor already uses, so they can tell in ten seconds whether their case is on the list.

How it works
  • The categories are content, not code: each one is a markdown file in a content collection, in Greek and English. A new category is a new file, not a template change.
  • Fully static: Astro 7 with Tailwind v4, no adapter and no server: every page is served as a file.
  • The typeface is self-hosted: Commissioner loads through Astro’s Fonts API, with no third-party request.
  • OG cards and the logo: per-route cards for the five main routes in both languages, rendered through Playwright, and the logo redrawn from the printed mark.

A guide for the cases that confuse people

Four of the twelve categories have a guide page of their own: social security, tax, disability certification and pensions.

The guide starts from why the case is hard and ends at the documents that come out of it. The visitor arrives at the office knowing what to ask for.

The social-security guide page: a link back to all services, two introductory paragraphs, and a list of what the bureau handles with each document name in bold
The social-security guide: the problem first, then the list of documents
How it works
  • One truth about which categories have a guide: a category gets a page only when its file has a non-empty body. The same check drives the build’s routes, the listing’s links and the llms.txt catalogue, so the three cannot disagree. Today four of the twelve have a body, and the other eight appear without a link.
  • Greek slugs in both languages: the English page lives under /en/ but keeps the Greek path, so one shared link works for both readers.
  • A single helper builds every locale-prefixed href, and a structural-parity test fails the build if the two dictionaries diverge in keys, types or array lengths.

A message instead of a phone queue

A visitor does not have to ring to ask whether their case is taken on: they pick a category, write their question and send it.

The opening hours are shown as they really are, with the evening window separate from the morning one, so nobody turns up at a closed door.

The contact page: a card with the bureau's details and day-by-day opening hours beside a form with name, phone, email, service category, message and a consent checkbox
The contact page: the bureau's details beside the form
How it works
  • The form goes through my own self-hosted forms service, with the public site key in the markup and no secret in the repository, and with Cloudflare Turnstile verified on the server side.
  • Consent: an explicit checkbox for storing the details, linked to the privacy policy, and GA4 that loads only behind the cookie consent’s analytics toggle.
  • JSON-LD ProfessionalService: a business-registry identifier, the VAT ID, a localized areaServed for city and country, separate morning and evening opening hours, and a disambiguatingDescription stating that the business is not a public authority.

It does not break quietly

A site that can fall behind without anyone saying so is worse than one that breaks. What exists in two copies here is derived from the one, and what could drift is caught by a gate before production.

How it works
  • A mirror for machines: 22 committed index.md twins and one llms-full.txt, with a <link rel="alternate" type="text/markdown"> on every route, so an AI assistant reads markdown, not HTML.
  • Build-time endpoints: llms.txt and robots.txt are derived from site.config.ts, the i18n dictionaries and the collection, through ordinary imports so the builder stays environment-independent.
  • Placeholder gate: it scans 7 files for scaffold strings that survived configuration, skipping comments and honouring sentinels for deliberately empty values.
  • The order of CI: types, unit tests, placeholder gate, build, a dead-link crawl of the built output, and then the e2e suites.
  • Seven Playwright suites: smoke, i18n, the form, responsive, an axe sweep over every page, and the display-preferences panel with its state matrix, plus a colour-contrast script over the rendered pages.
  • Two hosts, one build shape: production on Cloudflare Workers Static Assets through wrangler, staging as Docker on Coolify, with SITE_URL and STAGING_NOINDEX as build args so staging cannot claim production’s canonicals.
  • Rorsa Tools: the MCP toolkit behind this site’s markdown mirror
  • Κόμβος Υγείας & Dr Christos Kolettis: other Astro sites in the same family, without the AEO layer
Get in touch

Display preferences

Text

Text size
Line spacing
Letter spacing

Color

Contrast

Motion and images

Reading

Reading aid
No changes