Case study

feldstein.travel

A luxury travel-advisor site built like software: a hand-rolled design system, real logic at the edge, a grounded AI concierge, and a compliance discipline that holds up in a regulated category.

An independent luxury travel advisor needed a site that does three jobs at once. It has to look as refined as the cruise product it sells, turn browsers into qualified inquiries, and answer detailed pre-sale questions without a human on call. It also has to stay strictly compliant in an affiliate-marketing category, where a wrong phrase or an invented price is a real liability. Off-the-shelf themes and generic chatbots fail all three.

The answer was to build the marketing site like a piece of software. The result is feldstein.travel: a live, named site that reads as a polished luxury brand and runs as a real engineering system. It is owned by the practice and shown here with permission.

Visit the live site →

feldstein.travel homepage hero, set in Cormorant Garamond over a deep-ocean ship photograph, with a brass call-to-action button
The homepage hero. Display type in Cormorant Garamond, body in Inter, over the site’s sand, ocean, and brass palette. The concierge bubble sits bottom-right.

A hand-rolled design system, no framework

There is no Tailwind and no UI kit. The brand is held together by one CSS-custom-properties file of roughly three hundred lines: a palette, a fluid type scale, a spacing scale, and a small set of components. That single file keeps the look consistent across every page with no framework bloat, and it bakes accessibility into the tokens rather than bolting it on later.

Type specimen

Cormorant Garamond

Inter, the working sans for body and labels

A fluid scale runs from a 16px base up to an 88px hero step, set once as tokens and reused everywhere.

Accessibility lives inside the system, not in a checklist. The brass used for small text is a deliberately darker token documented in the source as clearing AA 4.5:1 contrast on sand surfaces. There is a global :focus-visible ring, a skip link to the main content, and a data table that collapses into stacked spec cards on narrow screens instead of forcing a horizontal scroll. The design discipline is the point, not a theme.

The tokens, verbatim

The accent-text token carries its own contrast note in the source:

:root {
  --paper:      #faf8f3;  /* sand-leaning page field */
  --sand:       #e8ddc8;
  --taupe:      #8b7e6a;
  --ocean:      #0f2d44;  /* primary deep blue */
  --brass:      #b8924a;  /* accent */
  --brass-text: #7a5d23;  /* clears AA 4.5:1 for small
                             text on sand surfaces */

  --font-serif: "Cormorant Garamond", Georgia, serif;
  --font-sans:  "Inter", system-ui, sans-serif;

  --step-0: 1rem;    /* 16  body  */
  --step-6: 4rem;    /* 64  display */
  --step-7: 5.5rem;  /* 88  hero  */
}
A data-driven voyages page on feldstein.travel listing itineraries with filters, a currency toggle, and a note that fares are refreshed daily
A programmatic voyages page. The itinerary list, filters, currency toggle, and the live-fares note are all driven by structured data.
The feldstein.travel homepage rendered at 390px wide, showing the responsive design-token layout
The same design tokens at 390px wide. The system is responsive by construction, not by override.

Real logic at the edge

The site is Astro 5 static output deployed to Cloudflare Pages. Voyage, region, port, and line-comparison pages are generated from structured data through programmatic routes rather than hand-built one by one, which is how a small set of templates becomes a large, cohesive site. The work that cannot be static lives in Cloudflare Pages Functions, the TypeScript edge layer. There are nine API functions, plus a routing middleware, doing genuine work rather than echoing form posts.

The inquiry endpoint refuses to lie

The lead-capture endpoint is hardened on purpose. It checks a honeypot field, validates the email with a strict single-address pattern that blocks header injection into outbound mail fields, and writes to Airtable with an upsert-by-email so a repeat inquiry updates the existing lead instead of creating a duplicate. It then sends an internal notification and a lead confirmation through a transactional email service. The senior detail is the failure path: if the lead persisted nowhere and no notification went out, the function returns a 503 rather than a cheerful thank-you page. It will not report success that did not happen.

A concierge that is grounded, not guessing

The on-site AI concierge, the Purser, is built for trust by construction. It embeds the visitor’s question with a Workers AI model, retrieves the most relevant passages from a curated knowledge base in Cloudflare Vectorize, and passes those passages to Claude as document blocks with citations turned on, routed through a Cloudflare AI Gateway. Before any reply reaches the visitor it runs through an output compliance gate. And there is a hard rule underneath all of it: fares never come from the model. The bot answers from sources or it says it cannot, which is exactly the AI-with-guardrails posture a regulated category demands.

Architecture of feldstein.travel A browser reaches Cloudflare Pages, which serves the static Astro site and routes two edge functions. The inquiry function writes to Airtable and sends mail through Resend. The chat function embeds the question with Workers AI, retrieves from Vectorize, calls Anthropic Claude with citations through an AI Gateway, then passes the reply through an output compliance gate. A daily fare scrape feeds GitHub Actions, which builds and deploys back to Pages. Browser visitor Cloudflare Pages static Astro 5 output middleware: 301 canonicalization /api/inquire honeypot, anti-injection upsert, 503 guard /api/chat the Purser RAG + citations Airtable Resend Vectorize + Workers AI embed Claude citations, AI Gateway output gate compliance Daily fare scrape FX, price history GitHub Actions CI
Static Astro on Cloudflare Pages, with edge functions handling inquiry and chat, and a daily scrape feeding the CI deploy. Diagram is illustrative of the live architecture.

Static output, living data

A static site does not have to mean stale content. A daily pipeline scrapes fares, refreshes foreign-exchange rates with a fallback, logs price history, and rebuilds the search index and the concierge catalog. Each build is gated by a content linter and a site-crawl linter, so nothing ships red. Deploys are a push to main on GitHub Actions, which builds and ships to Cloudflare Pages. The output is static and fast; the data behind it is fresh every day.

Multi-domain, one canonical home

Several domains point at the same brand, and the policy that resolves them lives in the repo, not in a dashboard. A Pages middleware issues a 301 from the alternate hosts and the www variant to the canonical feldstein.travel, and it carries a small map of renamed slugs so old, previously-indexed URLs redirect cleanly to their new homes. That keeps search equity in one place and keeps the canonicalization rules versioned alongside the code.

Why this is the hard part

Travel advising sits in an affiliate-marketing category with real compliance stakes, and the site treats that as an engineering constraint rather than a legal afterthought. The AI is grounded by retrieval and citations, every machine-written reply passes an output gate, and a verbatim independent-advisor disclosure appears on every page. The same compliance module that gates the live bot also runs in the content linter at build time, so the rules cannot drift between what gets written and what gets shipped.

The site footer showing the verbatim disclosure: Independent travel advisor, not affiliated with Explora Journeys or MSC Group, followed by a trademark-acknowledgment line
The required disclosure, verbatim and on every page: “Independent travel advisor, not affiliated with Explora Journeys or MSC Group.” with a trademark acknowledgment beneath it.

The stack

Astro 5 TypeScript Cloudflare Pages Pages Functions (edge) Pure-CSS design tokens Cormorant Garamond + Inter Vectorize + Workers AI Anthropic Claude (Citations) Airtable upsert Resend GitHub Actions CI/CD PWA

This case study documents the engineering and design of the live site. It does not publish traffic, conversion, or performance figures, none of which were measured for this write-up.

← Back to work